- 수정됨
mix and match skin attachments
A user emailed with an issue so I thought I'd share it here. They wanted (for example) a dragon with a normal head and a snake head with multiple images, and also normal wings and burning wings with multiple images. Because the multiple images are keyed in animations, a skin needs to be used so the animation can be reused. However, only one skin can be active at a time, so how can I mix and match the snake/normal head with burning/normal wings?
Well, you could set things up in Spine like this:
head (slot)
head1 (skin attachment)
normal head1 (region attachment)
snake head1 (region attachment)
head2 (skin attachment)
normal head2 (region attachment)
snake head2 (region attachment)
wing (slot)
wing1 (skin attachment)
normal wing1 (region attachment)
burning wing1 (region attachment)
wing2 (skin attachment)
normal wing2 (region attachment)
burning wing2 (region attachment)
normal head (skin)
head1 -> normal head1
head2 -> normal head2
snake head (skin)
head1 -> snake head1
head2 -> snake head2
normal wing (skin)
wing1 -> normal wing1
wing2 -> normal wing2
burning wing (skin)
wing1 -> burning wing1
wing2 -> burning wing2
You could then create a Skin at runtime where you combine these skins. Eg, for a snake head with burning wings, programmatically create a new skin like this:
snake head with burning wings (skin)
head1 -> snake head1
head2 -> snake head2
wing1 -> burning wing1
wing2 -> burning wing2
The alternative would be for me to change the runtimes so multiple skins can be active at the same time, however I think that would add more complexity than just creating a skin at runtime. It would also be less efficient, as we'd need to do a map look up for each active skin.
I try this code, but can't attach (normal head1) and (snake head1) to (head1 skin).
head (slot)
head1 (skin attachment)
normal head1 (region attachment)
snake head1 (region attachment)
I tried like this, but failed. It seems a skin sttachment can get one region attachment.
What am I doing wrong?
great quick question region attachment i attack my CCSprite ,FYI im on Cocos2d .
Ken wrotehead (slot)
head1 (skin attachment)
normal head1 (region attachment)
snake head1 (region attachment)I tried like this, but failed. It seems a skin attachment can get one region attachment.
What am I doing wrong?
Create "head1" (skin attachment). Click visibility dot next to "normal head" skin. Place "normal head1" image on "head1" (skin attachment). Click visibility dot next to "snake head" skin. Place "snake head1" image on "head1" (skin attachment). The skin video on this page may help:
http://esotericsoftware.com/spine-videos/
@kotaiba, you currently can't attach arbitrary cocos2d nodes as attachments. We we need a special kind of attachment.
Any future plan for that special attachment ?
Possibly, but I have so many other things to do at the moment it may be a while.
I made a match skin - region attachment successfully like sample code due to your advice. But I can click visibility just one skin so I can't turn on both [one head] and [one wing] at the same time. If I turn on a head, I can't turn on any wing. To make a animation, I need to see them all visible. In this case, I think it need "multiple turn on" of several skins in spine tool. But perhaps it harms to original skin function. How do you think about this?
Hmm. I will give it some thought. What do you think Shiu?
I like the idea. It requires a bit of thought to implement it so it's smooth to work with, but maybe it can be done.
Could you provide an example in ActionScript for this? I have tried to create a skin at runtime that tries to emulate more or less what you describe in your first post here. But the thing is when I try to use a skin I create at runtime the slots that are assigned attachments in that skin are shown as empty in the animation(s). If I switch to any of the other skins I have created at design time it works fine, but not with the runtime created one.
To me it seems that you cannot assign region attachments to skin attachments below a slot through the as3 spine framework. Only region attachmements directly to slots, and only if there are no skin attachments assign to that slot. Am I wrong in this assumption?
A skin attachment is just an attachment in a skin. The runtime object model isn't 1:1 with how things are arranged in the UI.
Can you post your code?
Note: this code is very simplified, but I wrote it as a proof of concept of a runtime created skin in actionscript. We are still trying to figure out if we are going to use skins or just region attachments with more overlapping slots or a mix of the two. I know that what this code does should not be done with a skin, but again this is just a simple proof of concept test.
The spine animation, for the section handled in the code below, has this structure:
hand2 (slot)
weapon (skin attachment)
v_arm_hand (region attachment)
v_arm_hand_club (region attachment)
v_arm_hand_sword (region attachment)
basic (skin)
weapon -> v_arm_hand
weapon_upgrade_0 (skin)
weapon -> v_arm_hand_club
weapon_upgrade_1 (skin)
weapon -> v_arm_hand_sword
public class SpinePOC extends Sprite
{
[Embed(source = "../assets/spine/hero04/LoC.xml", mimeType = "application/octet-stream")]
static public const HeroAtlas:Class;
[Embed(source = "../assets/spine/hero04/LoC.png")]
static public const HeroAtlasTexture:Class;
[Embed(source = "../assets/spine/hero04/skeleton.json", mimeType = "application/octet-stream")]
static public const HeroJson:Class;
private var atlasLoader:StarlingAtlasAttachmentLoader;
private var skeleton:SkeletonAnimationSprite;
public function SpinePOC()
{
var texture:Texture = Texture.fromBitmap(new HeroAtlasTexture());
var xml:XML = XML(new HeroAtlas());
var atlas:TextureAtlas = new TextureAtlas(texture, xml);
atlasLoader = new StarlingAtlasAttachmentLoader(atlas);
var json:SkeletonJson = new SkeletonJson(atlasLoader);
var skeletonData:SkeletonData = json.readSkeletonData(new HeroJson());
skeleton = new SkeletonAnimationSprite(skeletonData);
skeleton.setAnimation("idle", true);
// create a new skin
var skin1:Skin = new Skin("test_skin");
var idHand2:int = skeleton.skeleton.findSlotIndex("hand2");
// The below two lines produce the same result: empty slot
// "weapon" is the skin attachment name and "v_arm_hand_club" is the region attachment I try to attach to it
//skin1.addAttachment(idHand2, "weapon", new Attachment("v_arm_hand_club"));
skin1.addAttachment(idHand2, "weapon", atlasLoader.newAttachment(null, AttachmentType.region, "v_arm_hand_club"));
skeletonData.addSkin(skin1);
skeleton.skeleton.skin = skin1;
skeleton.skeleton.setToSetupPose();
addChild(skeleton);
Starling.juggler.add(skeleton);
}
}
If I do this:
skeleton.skeleton.skinName = "basic";
or
skeleton.skeleton.skinName = "weapon_upgrade_0";
then the animation has content in the hand2 slot. But my runtime created skin leaves the slot empty.
One thing I noticed is in the spin/Skin class there is this attachAll function. It has a for each loop that wants to loop through the properties of an object (oldSkin.attachments). If you loop through an object with a for each it will only return the objects property values (not the property keys). So if you change it to a normal for loop it does return the property keys instead and the rest of the code in there makes more sense But still after that change the slot.attachment is always null for the slots that have skin attachments. I don't know if this has anything to do with this, it is just an observation I made yesterday.
Thanks, fixed attachAll.
You need to call setToSetupPose after setting the skin or skinName. Changing the skin doesn't change the slots unless the last skin had an attachment in that slot (which is what the internal attachAll method does).
You're welcome
Well, I am calling setToSetupPose right after I set the skin to my runtime created one in the above example.
skeleton.skeleton.skin = skin1;
skeleton.skeleton.setToSetupPose();
The thing is even if I first set the skin to a designtime created skin and then try to set it to my runtime skin (even with doing setToSetupPose after each setting of skin) the loop inside Skin.attachAll never does anything because slot.attachment is always null for the slots that have a skin attachment. It's as if slot.attachment will only contain region attachments and not skin attachments. See my point?
But despite this it always works if I change between designtime created skins. It is only the runtime created skin I can't get to work.
Anyway, I am not at work now, so I'll look deeper into this tomorrow morning.
attachAll sets attachments if the old skin had attachments. If there is no old skin, it does nothing. I don't think attachAll is related to your problem, it is an internal method used to make setSkin behave as described in the setSkin method doc.
Ah, I see your problem now. setToSetupPose sets the slots to have the attachments that are used in the setup pose. Usually people show the skin attachments in the setup pose, then after setting the skin they call setToSetupPose to show attachments from the skin. You can see the setup pose attachments in the JSON:
"slots": [
{ "name": "root", "bone": "root", "color": "ff312fa5" },
{ "name": "left hand", "bone": "left hand", "attachment": "left-hand" },
...
Here the root has no setup pose attachment and the "left hand" slot will use the "left-hand" attachment. When you call setToSetupPose, whatever attachment is found in the skin with the name "left-hand" will be used. You can see the setup pose attachment in the object model, it's the SlotData "attachment" field.
I assume the attachments in your programmatically created skin are not the setup pose attachment for the slot, so setToSetupPose of course will not show them. What you actually want to do is to just set the attachments for your slots. You can do this by getting the slot and calling setAttachment. Your attachments don't need to be in any skin. A skin is just used to look up an attachment for a slot by a name.
The thing is that we are using keyed skin attachments in the animations, so just assigning region attachments to those slots does not make sense. Besides there is a runtime error if I try to assign a region attachment programmatically to a slot that has a skin attachment assigned in the UI.
skeleton.skeleton.setAttachment("hand2", "v_arm_hand__sword");
skeleton.skeleton.setToSetupPose();
results in:
ArgumentError: Attachment not found: v_arm_hand__sword, for slot: hand2
at spine::Skeleton/setAttachment()[C:\work\component\ngp\game_clients\slotmachines\qb2-DEV\legendsofcolosseum\src\spine\Skeleton.as:181]
at objects::SpinePOC()[C:\work\component\ngp\game_clients\slotmachines\qb2-DEV\legendsofcolosseum\src\objects\SpinePOC.as:101]
The setup pose attachments in the JSON looks like this:
// the "weapon" attachment is a skin attachment
"slots": [
...
{ "name": "hand2", "bone": "v_arm_hand", "attachment": "weapon" }
],
But I do not get a runtime error if I do like this:
skeleton.skeleton.setAttachment("hand2", "weapon");
skeleton.skeleton.setToSetupPose();
If I do the above for a skin created in the UI, nothing special happens (it looks the same as if I would not have made that setAttachment call, but the slot has content at least). But what does that line of code actually do? It reassigns the same skin attachment to the slot, but how to tell what region attachment should be linked to that skin attachment?
If I first set the skin to a UI created skin, do setToSetupPose and then apply a a programmatically created skin and do setToSetupPose and do the above setAttchment it renders the slot empty during the animation.
Is my way of creating a skin programmatically incorrect?
var skin1:Skin = new Skin("test");
var idHand2:int = skeleton.skeleton.findSlotIndex("hand2");
skin1.addAttachment(idHand2, "weapon", atlasLoader.newAttachment(null, AttachmentType.region, "v_arm_hand_club"));
skeletonData.addSkin(skin1);
skeleton.skeleton.skin = skin1;
skeleton.skeleton.setToSetupPose();
this has the same result:
var skin1:Skin = new Skin("test");
var idHand2:int = skeleton.skeleton.findSlotIndex("hand2");
skin1.addAttachment(idHand2, "weapon", atlasLoader.newAttachment(null, AttachmentType.region, "v_arm_hand_club"));
skeletonData.addSkin(skin1);
skeleton.skeleton.skin = skin1;
skeleton.skeleton.setToSetupPose();
// And the below lines should not be needed anyway, as the setting of the skin should do it
skeleton.skeleton.setAttachment("hand2", "weapon");
skeleton.skeleton.setToSetupPose();
Or is it in fact impossible to programmatically create a skin that attach different region attachment to slots with skin attachment? Because if it is not currently possible we have to try to use more slots with only region attachments instead. And I want to sort this before the graphics artist start to do too much work if he then is forced to redo it another way.
The reason we want to do it like this is that we have a character that can have 5 different races and 2 different genders and each slot uses several different keyed attachments and most of the slots also will equip different kinds of gear. That is why we need to be able to control it by code as not to be forced to create thousands of animations
Some progress.
If I first activate a skin with the desired attachment and retrieve that attachment and store it in a variable and then create my new skin and use that variable instead of using the atlasLoader it works fine.
skeleton.skeleton.skinName = "basic";
skeleton.skeleton.setToSetupPose();
var idHand2:int = skeleton.skeleton.findSlotIndex("hand2");
var attachment:Attachment = skeleton.skeleton.getAttachmentForSlotIndex(idHand2, "weapon");
skin1.addAttachment(idHand2, "weapon", attachment);
skeletonData.addSkin(skin1);
skeleton.skeleton.skin = skin1;
skeleton.skeleton.setToSetupPose();
Is this how I have to go by? Would be nice if I could do this directly by using the atlasLoader so I don't have to activate a skin for each slot to pick all the attchments I want to use.
I typed a whole response earlier, no idea where it went. Here goes again...
A skin is a mapping of slot+name to attachment. A skin is only used to look up an attachment for a slot by a name.
Animations that key image changes find the attachment to use by name. In this case you are right that you need to put the attachments in the skin so that the animations can find them by name.
skeleton.skeleton.setAttachment("hand2", "weapon");
[snip] what does that line of code actually do?
The line of code looks in the skin for an attachment with the name "weapon" for the "hand2" slot. If not found it looks in the default skin. If not found in the default skin either, the "Attachment not found:" error is thrown. If found it sets the attachment on the slot.
It reassigns the same skin attachment to the slot, but how to tell what region attachment should be linked to that skin attachment?
This is confusing. In the object model there is no such thing as skin attachment. Let me explain.
An attachment has a name, eg "head-green". A skin is a slot+name to attachment mapping. Eg, a skin might have a key [slot: "head", name: "head"] and a value for that key [attachment "head-green"]. Note the name in the skin key doesn't have to match the actual name of the attachment. Another skin might have a key [slot: "head", name: "head"] and a value [attachment "head-purple"]. The skin allows the name in the skin ("head") to be used without knowing which attachment is actually used ("head-green" or "head-purple").
When you call skeleton.setAttachment("head", "head"), it looks in the skin using the key [slot: "head", name: "head"]. It takes the attachment found for that key and sets the slot's attachment. Internally it is doing code like this:
int slotIndex = skeleton.findSlotIndex("head");
Attachment attachment = skin.getAttachment(slotIndex, "head");
Slot slot = skeleton.getSlots().get(slotIndex);
slot.setAttachment(attachment);
What you need to do is create a new skin and add your attachments to it. When you set the skin on a skeleton this doesn't change any of the slot attachments, since it doesn't know which attachments you might want to set. Setting a skeleton's skin just means that skeleton.setAttachment will look in that skin to find an attachment. setToSetupPose() won't help you show your attachments if the attachments are not visible in the setup pose. In that case you can just show the attachments you want shown, eg:
RegionAttachment attachment = ...
Slot slot = skeleton.findSlot("head");
slot.setAttachment(attachment);
Finally, when you run your animations any image change keys will try to find an attachment by name. It will look in the skin (eg under the name "head") and find your attachment (eg with the name "head-purple").
Another thing that just occurred to me is that atlasLoader.newAttachment() only creates a new attachment. It is not sufficient to create a region attachment that can be drawn. You need to add some code that defines the image size and attachment offset relative to the bone. See the code here:
https://github.com/EsotericSoftware/spi ... on.cs#L159
At the very least, to get the region attachment to draw, set the width and height and call UpdateOffset().
In Spine it is easy to position the image in the right place. This is the downside to creating attachments programmatically. You need to specify how the attachment is positioned relative to the bone. You can do this if you are careful with how you create your art assets, eg by positioning all region attachments the same way.