- Edited
Issue creating a custom skin in Unity
hey peeps! I'm new around here. Been working on a project in Unity, and I'm wondering if anyone has any sage advice. I'm creating a custom skin by duplicating the currently equipped skin, and setting (or removing) attachments as necessary.
For instance, for the head, I am setting the Head, Main_Hair, Side_Hair, and Mask attachments. In this particular example, I'm trying to set the Head attachment, and remove Main_Hair, Side_Hair and Mask.
For some reason, the result is that the Head attachment changes, and the Hair slots stay the same. I've even debugged, and the hair slots should be getting removed.
customSkin = customSkin.GetClone();
// set or remove slots as necessary for the new custom skin
foreach (var skinSlot in skinSlots)
{
var newAttachment = skin.GetAttachment(skinSlot.slot, skinSlot.key, skeleton);
if (newAttachment != null)
{
customSkin.SetAttachment(skinSlot.slot, skinSlot.key, newAttachment, skeleton);
}
else
{
customSkin.RemoveAttachment(skinSlot.slot, skinSlot.key, skeleton);
}
}
// if I check the customSkin here before setting, it appears to be correct. the hair attachments do not appear in the collection
skeleton.SetSkin(customSkin);
I was expectig that if newAttachment returned null, I would just remove any attachment from the customSkin, and that this slot would just be empty. Not sure what's actually happening.
I seem to have figured out a solution, but it required a slight change to the SDK code.
if (slot.Attachment == entry.Value) {
Attachment attachment = GetAttachment(slotIndex, entry.Key.name);
if (attachment != null) slot.Attachment = attachment;
}
the
if (attachment != null)
line makes it such that applying a skin with less attachments will skip any attachments that are not provided. I think this is so you can supply a "skin" that contains only the clothing, and the other sprites won't change. So if I pass in a skin with a helmet (no hair), the hair just stays there.
I think I follow the reasoning there, it's just not quite what I wanted to do with my code.
I don't know how dirty this is, but I suggest an overload. skipNullAttachments (default = true to follow current convention)
// in Skin.cs
internal void AttachAll (Skeleton skeleton, Skin oldSkin, bool skipNullAttachments = true) {
foreach (KeyValuePair<AttachmentKeyTuple, Attachment> entry in oldSkin.attachments) {
int slotIndex = entry.Key.slotIndex;
Slot slot = skeleton.slots.Items[slotIndex];
if (slot.Attachment == entry.Value) {
Attachment attachment = GetAttachment(slotIndex, entry.Key.name);
if (skipNullAttachments && attachment == null)
continue;
slot.Attachment = attachment;
}
}
}
and
// in Skeleton.cs
public void SetSkin (Skin newSkin, bool skipNullAttachments = true) {
if (newSkin != null) {
if (skin != null)
newSkin.AttachAll(this, skin, skipNullAttachments);
else {
ExposedList<Slot> slots = this.slots;
for (int i = 0, n = slots.Count; i < n; i++) {
Slot slot = slots.Items[i];
string name = slot.data.attachmentName;
if (name != null) {
Attachment attachment = newSkin.GetAttachment(i, name);
if (skipNullAttachments && attachment == null)
continue;
slot.Attachment = attachment;
}
}
}
}
skin = newSkin;
}
You can read more on the discussion on this behavior here: [runtimes] Should SetSkin null slots? · #506
The short of it for now is just simply don't rely on SetSkin, by itself in particular, to do what you need in this situation.
For now, the recommendation is to call skeleton.SetSlotsToSetupPose, then optionally, animationState.Apply(skeleton), to ensure the slots reflect what the skin specifies.
The alternative is, if you know which slot and attachment is being modified, just call slot.SetToSetupPose, or slot.Attachment = yourAttachment, whenever you modify the skin.
Ok thank you for the help. But what about repacking skins at runtime to save draw calls / texture memory?
What about it?