- Düzenlendi
[tk2d] Creating skin programmatically
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.
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();
}
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.
- Düzenlendi
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.
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.
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();
}
}
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)
1, Int32, Int32) (at Assets/Libraries/lib_unity_SlotsClient/Assets/Scripts/Slots/Spine/spine-unity/Modules/SkeletonRenderSeparator/SkeletonPartsRenderer.cs:78)
Spine.Unity.Modules.SkeletonPartsRenderer:RenderParts(ExposedList
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