• Runtimes
  • [tk2d] Creating skin programmatically

  • Düzenlendi

Hello!
I just got Spine last night and I think it's great. I do have a question regarding creating skins programmatically though.

I'm using 2d toolkit and I'm trying to create a skeleton with attachments that are a composite of sprites from different sprite collections. I don't even know if this is possible, but I thought I'd give it a go.

This is the code I use to create and set the custom skin...

Skin skin = new Skin("Custom");
SpriteCollectionAttachmentLoader spriteCollectionAttachmentLoader = new SpriteCollectionAttachmentLoader(spritecollectionData);
int slotIndexForHead = skeleton.FindSlotIndex("Head01");
skin.AddAttachment(slotIndexForHead, "Leg01", spriteCollectionAttachmentLoader.NewAttachment(skin, AttachmentType.region, "Leg01"));
skeleton.data.AddSkin(skin);
skeleton.skin = skin;
skeleton.SetSkin("Custom");
skeleton.SetToSetupPose();

...but it doesn't seem to do anything. The skin is added (I can select it in the SkeletonAnimation's "Initial skin"-combobox in Unity). But the attachment "Head01" still shows the original attachment. Not "Leg01".

I'm a Spine noob so I might've very well misunderstood how I'm supposed to create skins in code, so any help would be appreciated!

Regards/Per

That code looks ok. You only need SetSlotsToSetupPose, not SetToSetupPose, but that won't fix your issue. After SetSkin and SetSlotsToSetupPose, try calling skeleton.GetAttachment and verify the slot has the attachment you expect it to have. Also verify that the NewAttachment method is assigning the correct region to the attachment.

24 gün sonra

I am also having trouble with creating a new skin and getting it to appear... any help would be appreciated

Any luck with this Shaka?

This is the example code I'm using, basically I'm creating a clone of the current skin, then applying it. But my Spine character appears blank afterwards (all images dissapear when I set the skin and call SetSlotsToSetupPose)

The skin appears to by applied correctly afterwards, I can verify the attachments by calling skeleton.GetAttachment() so I think it may be something to do with SpriteCollectionAttachmentLoader not creating the NewAttachment correctly, but then why would this work differently from when it is used in Initialisation?

void cloneCurrentSkinAndApply(){
		
	Spine.Skeleton skeleton = skelAnim.skeleton;
	Spine.Skin currSkin = skeleton.Skin;
	
	//create new skin to populate
	Spine.Skin newSkin = new Spine.Skin("New Skin");
	
	//create an attachment loader to use
	SpriteCollectionAttachmentLoader spriteCollectionAttachmentLoader = new SpriteCollectionAttachmentLoader(skelAnim.skeletonDataAsset.spriteCollection);
	
	//vars to use in loop
	System.Collections.Generic.List<Spine.Slot> slots = skeleton.slots;
	System.Collections.Generic.List<string> attachmentNames;
	System.Collections.Generic.List<Spine.Attachment> slotAttachments; 
	
	
	// for each slot in the skin, get the attachment names,  image names, make copies of attachments to the new skin
	foreach(Spine.Slot slot in slots){
		
		//get the slot index
		int slotIndex = skeleton.FindSlotIndex(slot.data.name);
		
		//get attachments from this slot
		slotAttachments = new System.Collections.Generic.List<Spine.Attachment>();
		currSkin.FindAttachmentsForSlot(slotIndex,slotAttachments);
		
		//get the names 
		attachmentNames = new System.Collections.Generic.List<string>();
		currSkin.FindNamesForSlot(slotIndex,attachmentNames);
		
		//for each attachment in this slot, copy it to new skin
		for(int i=0;i<slotAttachments.Count;i++){
			
			Spine.Attachment att = slotAttachments[i];
			
			string attName = attachmentNames[i];
			string spriteImageName = att.Name;
			
			Debug.Log("In Slot "+slotIndex+" '"+slot.data.name+"', add attachment '"+attName+"' source image name:'"+spriteImageName+"'");
			Spine.Attachment newAtt = spriteCollectionAttachmentLoader.NewAttachment(newSkin, Spine.AttachmentType.region, spriteImageName);
			newSkin.AddAttachment(slotIndex, attName, newAtt);
		}
	}
	
	skeleton.data.AddSkin(newSkin);
	skeleton.SetSkin(newSkin);
	skeleton.SetSlotsToSetupPose();
}

Update... got it working 🙂

needed to set properties on the new RegionAttachment

code below again, copies current skin to new one and applies it.

void cloneCurrentSkinAndApply(){
		
	Spine.Skeleton skeleton = skelAnim.skeleton;
	Spine.Skin currSkin = skeleton.Skin;
	
	//create new skin to populate
	Spine.Skin newSkin = new Spine.Skin("New Skin");
	
	//create an attachment loader to use
	SpriteCollectionAttachmentLoader spriteCollectionAttachmentLoader = new SpriteCollectionAttachmentLoader(skelAnim.skeletonDataAsset.spriteCollection);
	
		
	//vars to use in loop
	System.Collections.Generic.List<Spine.Slot> slots = skeleton.slots;
	System.Collections.Generic.List<string> attachmentNames;
	System.Collections.Generic.List<Spine.Attachment> slotAttachments; 
	
	
	// for each slot in the skin, get the attachment names,  image names, make copy of the attachement to the new skin
	foreach(Spine.Slot slot in slots){
		
		//get the slot index
		int slotIndex = skeleton.FindSlotIndex(slot.data.name);
		
		//get attachments from this slot
		slotAttachments = new System.Collections.Generic.List<Spine.Attachment>();
		currSkin.FindAttachmentsForSlot(slotIndex,slotAttachments);
		
		//get the names 
		attachmentNames = new System.Collections.Generic.List<string>();
		currSkin.FindNamesForSlot(slotIndex,attachmentNames);
		
		//for each attachment in this slot, copy it to new skin
		for(int i=0;i<slotAttachments.Count;i++){
			
			Spine.Attachment att = slotAttachments[i];
			
			string attName = attachmentNames[i];
			string spriteImageName = att.Name;
			
			Debug.Log("In Slot "+slotIndex+" '"+slot.data.name+"', add attachment '"+attName+"' source image name:'"+spriteImageName+"'");
			Spine.Attachment newAtt = spriteCollectionAttachmentLoader.NewAttachment(newSkin, Spine.AttachmentType.region, spriteImageName);
			
			Spine.RegionAttachment newRegAtt = newAtt as Spine.RegionAttachment;
			Spine.RegionAttachment oldRegAtt = att as Spine.RegionAttachment;
			
			//copy properties from old regionattachmet to new one
			newRegAtt.x = oldRegAtt.x;
			newRegAtt.y = oldRegAtt.y;
			newRegAtt.scaleX = oldRegAtt.scaleX;
			newRegAtt.scaleY = oldRegAtt.scaleY;
			newRegAtt.rotation = oldRegAtt.rotation;
			newRegAtt.width = oldRegAtt.width;
			newRegAtt.height = oldRegAtt.height;
			newRegAtt.UpdateOffset();
		
			newSkin.AddAttachment(slotIndex, attName, newRegAtt);
			
		}
	}
	
	
	skeleton.data.AddSkin(newSkin);
	skeleton.SetSkin(newSkin);
	skeleton.SetSlotsToSetupPose();
}
6 ay sonra
rjohnn yazdı

Update... got it working 🙂

needed to set properties on the new RegionAttachment

code below again, copies current skin to new one and applies it.

void cloneCurrentSkinAndApply(){
      
Spine.Skeleton skeleton = skelAnim.skeleton; Spine.Skin currSkin = skeleton.Skin; //create new skin to populate Spine.Skin newSkin = new Spine.Skin("New Skin"); //create an attachment loader to use SpriteCollectionAttachmentLoader spriteCollectionAttachmentLoader = new SpriteCollectionAttachmentLoader(skelAnim.skeletonDataAsset.spriteCollection); //vars to use in loop System.Collections.Generic.List<Spine.Slot> slots = skeleton.slots; System.Collections.Generic.List<string> attachmentNames; System.Collections.Generic.List<Spine.Attachment> slotAttachments; // for each slot in the skin, get the attachment names, image names, make copy of the attachement to the new skin foreach(Spine.Slot slot in slots){ //get the slot index int slotIndex = skeleton.FindSlotIndex(slot.data.name); //get attachments from this slot slotAttachments = new System.Collections.Generic.List<Spine.Attachment>(); currSkin.FindAttachmentsForSlot(slotIndex,slotAttachments); //get the names attachmentNames = new System.Collections.Generic.List<string>(); currSkin.FindNamesForSlot(slotIndex,attachmentNames); //for each attachment in this slot, copy it to new skin for(int i=0;i<slotAttachments.Count;i++){ Spine.Attachment att = slotAttachments[i]; string attName = attachmentNames[i]; string spriteImageName = att.Name; Debug.Log("In Slot "+slotIndex+" '"+slot.data.name+"', add attachment '"+attName+"' source image name:'"+spriteImageName+"'"); Spine.Attachment newAtt = spriteCollectionAttachmentLoader.NewAttachment(newSkin, Spine.AttachmentType.region, spriteImageName); Spine.RegionAttachment newRegAtt = newAtt as Spine.RegionAttachment; Spine.RegionAttachment oldRegAtt = att as Spine.RegionAttachment; //copy properties from old regionattachmet to new one newRegAtt.x = oldRegAtt.x; newRegAtt.y = oldRegAtt.y; newRegAtt.scaleX = oldRegAtt.scaleX; newRegAtt.scaleY = oldRegAtt.scaleY; newRegAtt.rotation = oldRegAtt.rotation; newRegAtt.width = oldRegAtt.width; newRegAtt.height = oldRegAtt.height; newRegAtt.UpdateOffset(); newSkin.AddAttachment(slotIndex, attName, newRegAtt); } } skeleton.data.AddSkin(newSkin); skeleton.SetSkin(newSkin); skeleton.SetSlotsToSetupPose(); }

Bump this,

I update this code to work with the new runtime implementations.

Change this line:
Spine.Attachment newAtt = spriteCollectionAttachmentLoader.NewAttachment(newSkin, Spine.AttachmentType.region, spriteImageName);

For this:
Spine.Attachment newAtt = spriteCollectionAttachmentLoader.NewRegionAttachment(newSkin, spriteImageName, spriteImageName);

But if I add FFD to the skeleton this code doesn't work. What I did is add this code:

if(att is Spine.MeshAttachment)
               {
                  Spine.Attachment newMeshAtt = spriteCollectionAttachmentLoader.NewMeshAttachment(newSkin,spriteImageName,spriteImageName);

              Spine.MeshAttachment NewMatt = newMeshAtt as Spine.MeshAttachment;
              Spine.MeshAttachment oldMatt = att as Spine.MeshAttachment;

              NewMatt.vertices = oldMatt.vertices;
              NewMatt.UVs = oldMatt.UVs;
              NewMatt.regionUVs = oldMatt.regionUVs;
              NewMatt.Triangles = oldMatt.Triangles;
              

              newSkin.AddAttachment(slotIndex, attName, NewMatt);
           }

Now the MeshAttachments works, but they are not doing changes when the animations have keyframes with FFD. They are just visible like setup mode.

What is wrong?

Thank you

Different attachments may have different meshes, so FFD is per done per attachment. FfdTimline is only applied if the current attachment matches the FfdTimline's attachment.

For this line:

Spine.Attachment newAtt = spriteCollectionAttachmentLoader.NewRegionAttachment(newSkin, spriteImageName, spriteImageName);

I think I've found that the second "spriteImageName" is expecting a path, but a path to what exactly? Nothing I try seems to work.

Edit: Okay, looks like I got it, but I don't understand why. I was trying the path to the sprite collection, "Assets/Collections/Accessories", which throws an error saying that the sprite "Accessories" was not found, even though I was just trying to supply the folder. So I changed it to "Assets/Collections/hat", "hat" being the name of the sprite I was trying to create an attachment for, and for whatever reason it works, even though the path isn't specifying the "Accessories" folder. I don't get it, but hey, it works.

The path is what is used to find the image. Most loaders don't use the name, except for error messages. Usually the path and name are the same, but not always. Passing spriteImageName for both is fine.

Nate yazdı

The path is what is used to find the image. Most loaders don't use the name, except for error messages. Usually the path and name are the same, but not always. Passing spriteImageName for both is fine.

I don't know what was going wrong before... I tried putting my sprite name for both and it didn't work, but now that I tried it again so I could give you the error message, it works. :o Eh, I probably screwed something else up. Thanks.

Nate yazdı

Different attachments may have different meshes, so FFD is per done per attachment. FfdTimline is only applied if the current attachment matches the FfdTimline's attachment.

I think I don't follow you,

Do I need to copy the FfdTimLine from the old skin to the new skin?

This code just copy the skin attachments to a new runtime skin. What I need to copy in order to make FFD works?

FfdTimeline is a timeline. Animations have a list of timelines. For every animation, you'd need to copy any FfdTimeline who has the attachment that has the FFD you want to copy.

Take a look at this task I just added, and especially the thread linked from the task:
https://trello.com/c/ljyJfNVt/63-way-to ... -be-reused

Nate yazdı

FfdTimeline is a timeline. Animations have a list of timelines. For every animation, you'd need to copy any FfdTimeline who has the attachment that has the FFD you want to copy.

Take a look at this task I just added, and especially the thread linked from the task:
https://trello.com/c/ljyJfNVt/63-way-to ... -be-reused

I don't get why from all timelines the only that don't work is FFDTimeLine, When I use this script all works perfect (Animations, Colors, events, etc), without need of copy any timeline or change anything.

From the post you linked, I am on the group that get advantage from tweak every FFD as unique.

From what you say, copy the FFDTimeLine for every animation who has attachments with FFD sounds very complex.

Can you post a code how to do that with my script?

Well, it would be good if you explain exactly what you are trying to achieve.

Nate yazdı

Well, it would be good if you explain exactly what you are trying to achieve.

Im copying skins with attachments(some have FFD) to create a new runtime skin.

My skins are divided in body parts. Ex: Head1, Head2, Body1, Body2, Legs1, legs2.
At runtime I make a random combination, ex: Head2, body1, legs1. Then I create the skin with this script.
All works well except from animated FFD.

In Spine they look like:

Skin: HEAD1
Slot: HEAD
Skin placeholder: regular head
MeshAttachment: regular-head1
Skin placeholder: big head
Attachment: big-head1

You don't need to create a copy of an attachment to put it in a new skin. Attachments are stateless. The same attachment can be in multiple skins, attached to multiple skeletons, etc.

Nate yazdı

You don't need to create a copy of an attachment to put it in a new skin. Attachments are stateless. The same attachment can be in multiple skins, attached to multiple skeletons, etc.

I add a copy of the skin to the new runtime skin. So the new runtime skin is a combination of multiples skins.

Ex:
New Runtime skin:
Head1 skin
Body2 skin
Legs1 skin

All merged in one skin.

Create a new, empty skin. Iterate each attachment in the Head1 skin and add it to the new skin. You don't need to make a copy of the attachment. Do the same for Body2 and Legs1 skins. The resulting skin has the attachments from the Head1, Body2 and Legs1 skins.

Yeah I do that, what Im copying is the properties, triangles, uvs, etc. If i don't that the attachments in the new skin are not visible in unity.
Look at the post of the guy that created the script, He had the same problem and the solution was copying the properties.

Anyway, if I do what you say the FFD animations will work?

I don't think the OP is doing the same. You do not need to create new attachments, you can just add the existing attachment to multiple skins. You shouldn't create new attachments. If it doesn't render, focus on that problem, not on copying the attachments.

Yes, if you copy the FFD timelines and change the attachment to the new attachment then your new attachment will animate using the same FFD.

Im talking about rjohnn posts. I have the same problem when I added the MeshAttachments code lines

               if (att is Spine.MeshAttachment)
               {
                                    Spine.Attachment newMeshAtt = spriteCollectionAttachmentLoader.NewMeshAttachment(newSkin,spriteImageName,spriteImageName);

              Spine.MeshAttachment NewMatt = newMeshAtt as Spine.MeshAttachment;
              Spine.MeshAttachment oldMatt = att as Spine.MeshAttachment;

              NewMatt.vertices = oldMatt.vertices;
              NewMatt.UVs = oldMatt.UVs;
              NewMatt.regionUVs = oldMatt.regionUVs;
              NewMatt.Triangles = oldMatt.Triangles;


              newSkin.AddAttachment(slotIndex, attName, NewMatt);
           }

newSkin is the empty new skin created, As you see, what I'm doing is adding the attachments to the new skin. If I don't copy the NewMatt properties from the oldMatt the attachments are not visible. I don't know why but It works.

So how do I have access to the timelines and copy them to the new skin?

Replace all of that with:

newSkin.AddAttachment(slotIndex, attName, att);

Thank you Nate it works!!

Also one more thing, maybe I need to open a new thread.

TK2D now have 2 options to save the atlas format: Unity texture, and png.

The png format doesn't work. Unity shows empty sprites.

Glad it works in a much simpler way. 🙂

I committed a fix, discussed here:
viewtopic.php?p=10899#p10899
I found def.material == def.materialInst when using "Unity texture" but not when using PNG, so I'm hoping it's ok to use all the time.

5 ay sonra

Hello!
I need to create skin programmatically.
My platform is Cocos2d 2.0.
I can't find any method like addSkin() in SkeletonData.h like above.
Is there any to do like that?

Thank you.

bir yıl sonra

If anyone like me got here looking for a way to put TextureRegion inside Spine animation programmatically (by replacing one of attachments) here's your answer in LibGDX:

/**
 * SpineUtils.putRegionIntoSpineSkin() allows to programmatically put LibGDX TextureRegion into Spine animation slot by replacing existing attachment in cloned spine skin. This way you don't have to change SkeletonData so you can use it for multiple spine animations with LibGDX AssetManager.
 *
 * @author Lukasz Zmudziak, @"lukz_dev"#4132 on 2016-01-11.
 */
public class SpineUtils {

    public static Skin cloneSkin(Skeleton skeleton, Skin oldSkin, String newName) {
        Skin newSkin = new Skin(newName);

Array<Attachment> slotAttachments = new Array<Attachment>();
Array<String> attachmentNames = new Array<String>();

// Loop through every slot
for(Slot slot : skeleton.getSlots()) {

    // Get the slot index
    int slotIndex = skeleton.findSlotIndex(slot.getData().getName());

    // Get attachments and names from this slot
    oldSkin.findAttachmentsForSlot(slotIndex, slotAttachments);
    oldSkin.findNamesForSlot(slotIndex, attachmentNames);

    // For each attachment in this slot, copy it to new skin
    for(int i = 0; i < slotAttachments.size; i++) {
        newSkin.addAttachment(slotIndex, attachmentNames.get(i), slotAttachments.get(i));
    }

    // Clear arrays
    slotAttachments.clear();
    attachmentNames.clear();
}

return newSkin;
    }

/**
 * Makes a copy of default skin and replace attachment with the new one
 * @param spineAnim
 * @param region
 * @param slot name of slot that contains replaced attachment
 */
public static void putRegionIntoSpineSkin(SpineAnimationComponent spineAnim, TextureRegion region, String slot) {
    // Get old attachment
    RegionAttachment oldAttach = (RegionAttachment)spineAnim.skeleton.findSlot(slot).getAttachment();

    // Prepare new attachment
    RegionAttachment newAttach = new RegionAttachment("Attachment");
    newAttach.setRegion(region);
    newAttach.setWidth(region.getRegionWidth());
    newAttach.setHeight(region.getRegionHeight());
    newAttach.setScaleX(Assets.ASSET_RESOLUTION_SCALE); // This is specific for my code
    newAttach.setScaleY(Assets.ASSET_RESOLUTION_SCALE); // This is specific for my code
    newAttach.setRotation(oldAttach.getRotation());
    newAttach.getColor().set(oldAttach.getColor());
    newAttach.setX(oldAttach.getX());
    newAttach.setY(oldAttach.getY());
    newAttach.updateOffset();

    // Find proper slot
    int slotIndex = spineAnim.skeleton.findSlotIndex(slot);

    // Prepare new skin and replace attachment with the new one
    Skin skin = SpineUtils.cloneSkin(spineAnim.skeleton, spineAnim.skeleton.getData().getDefaultSkin(), "Custom");
    skin.addAttachment(slotIndex, oldAttach.getName(), newAttach);

    // Set up new skin
    spineAnim.skeleton.setSkin(skin);
    spineAnim.skeleton.setSlotsToSetupPose();
}
}
2 yıl sonra
rjohnn yazdı

if(att is Spine.MeshAttachment)
{
Spine.Attachment newMeshAtt = spriteCollectionAttachmentLoader.NewMeshAttachment(newSkin,spriteImageName,spriteImageName);

              Spine.MeshAttachment NewMatt = newMeshAtt as Spine.MeshAttachment;
              Spine.MeshAttachment oldMatt = att as Spine.MeshAttachment;

              NewMatt.vertices = oldMatt.vertices;
              NewMatt.UVs = oldMatt.UVs;
              NewMatt.regionUVs = oldMatt.regionUVs;
              NewMatt.Triangles = oldMatt.Triangles;
              

              newSkin.AddAttachment(slotIndex, attName, NewMatt);

}

I've trying to get Spine Skin cloning working in order to easily switch out materials on spine objects. The post by rjohnn for cloning RegionAttachments works great. However, I'm finding Copying the MeshAttachment doesn't work. Not sure what the issue is, the code seems correct. Unity gives me this:

Failed setting triangles. Some indices are referencing out of bounds vertices. IndexCount: 198, VertexCount: 24
UnityEngine.Mesh:SetTriangles(Int32[], Int32)
Spine.Unity.MeshGeneration.ArraysSubmeshSetMeshGenerator:GenerateMesh(ExposedList1, Int32, Int32) (at Assets/Libraries/lib_unity_SlotsClient/Assets/Scripts/Slots/Spine/spine-unity/Mesh Generation/Arrays/ArraysSubmeshSetMeshGenerator.cs:134)
Spine.Unity.Modules.SkeletonPartsRenderer:RenderParts(ExposedList
1, Int32, Int32) (at Assets/Libraries/lib_unity_SlotsClient/Assets/Scripts/Slots/Spine/spine-unity/Modules/SkeletonRenderSeparator/SkeletonPartsRenderer.cs:78)
Spine.Unity.Modules.SkeletonRenderSeparator:HandleRender(Instruction) (at Assets/Libraries/lib_unity_SlotsClient/Assets/Scripts/Slots/Spine/spine-unity/Modules/SkeletonRenderSeparator/SkeletonRenderSeparator.cs:155)
Spine.Unity.SkeletonRenderer:LateUpdateInternal() (at Assets/Libraries/lib_unity_SlotsClient/Assets/Scripts/Slots/Spine/spine-unity/SkeletonRenderer.cs:464)
Spine.Unity.SkeletonRenderer:LateUpdate() (at Assets/Libraries/lib_unity_SlotsClient/Assets/Scripts/Slots/Spine/spine-unity/SkeletonRenderer.cs:269)

I've been trying to trace this back but can't find a solution so far, maybe someone out there knows the answer.

Nate yazdı

Replace all of that with:
newSkin.AddAttachment(slotIndex, attName, att);

Seems incorrect, when I do this it only adds the existing attachments without my material applied. Am I missing something here?

Thanks much,

Jason

I think you may have read advice/copied code from much older versions of the Spine runtime.

Spine-Unity now comes with some extension methods for easy cloning of attachments. They may become part of the core runtime in the future.

Make sure you add the using for it.

using Spine.Unity.Modules.AttachmentTools;

and to get a clone of an attachment, you just call:

var clonedAttachment = myAttachment.GetClone(true); // true param means mesh attachments will be cloned as linked meshes so they will obey Deform animations from the source attachments.

You can find the code here: spine-runtimes/AttachmentTools.cs at 3.6

Great thanks Pharan! extremely helpful.

-Jason