• Runtimes
  • How to set dynamic texture on skeleton

Hi all,
I'm stumped on something that seems like it should be easy but I feel like I'm swimming upstream and I can't seem to make sense of the docs on setting dynamic skins.

I'm one of the contributors to the FlatRedBall game engine (which is based on MonoGame) and we use the MonoGame runtime with a light wrapper for rendering in our engine. So the code I'm using is nearly pure MonoGame runtime and we have tested rigorously enough to be confident that everything is working. So, my problem is not a problem with the FlatRedBall or MonoGame SDK but rather understanding how it is intended to be used.

I have a Spine skeleton .json exported to my test project with a single skin called "template". This skin sets a default head, body, feet, and weapon texture on a simple skeleton. Here is a screenshot of my Spine setup:

I'd like to be able to swap these textures at runtime with textures from an atlas. Here is the C# method I have written to swap the texture used for a provided placeholder name:

public void SetTextureForPlaceholder(string placeholderName, string atlasTextureToSet, Atlas atlas)
{
    var dynamicSkin = new Skin("dynamicSkin");
    var templateSkin = SpineInstance.Skeleton.Data.FindSkin("template");

    // copy the template to the dynamic skin to start
    dynamicSkin.CopySkin(templateSkin);

    // get the placeholder slot from the template skin
    var existingSlot = dynamicSkin.Attachments
        .Where(a => a.Name == placeholderName)
        .FirstOrDefault();
    var existingRegion = existingSlot.Attachment as RegionAttachment;

    // create a new region that's a copy of the one we got from the template skin
    var dynamicAttachment = new RegionAttachment(existingRegion);

    // get dynamic region data from the atlas and set the texture on our dynamic region
    var regionData = atlas.Where(r => r.name == atlasTextureToSet).First();
    dynamicAttachment.Region = regionData;

    // now set the attachment on the dynamic skin
    dynamicSkin.SetAttachment(existingSlot.SlotIndex, existingSlot.Name, dynamicAttachment);

    // apply the dynamic skin to the skeleton
    SpineInstance.Skeleton.SetSkin(dynamicSkin);
}

This method appears to work. When I inspect the object, I can see the region data for the specific slot I'm trying to change appears to update. There are no errors thrown but the visible appearance of the skeleton does not change.

What am I doing wrong and is there a more simple way than this hacky system of copying and altering existing components?

Related Discussions
...

To set dynamic textures on a Spine skeleton at runtime, you need to ensure that the new textures are correctly applied to the skeleton's attachments and that the skeleton is updated to reflect these changes. Here is a refined approach based on the provided contexts and your current method:

  1. Ensure the new texture region is correctly set on the attachment.
  2. Update the skeleton to use the new skin.
  3. Apply the changes to the skeleton's animation state.

Here is an updated version of your method:

public void SetTextureForPlaceholder(string placeholderName, string atlasTextureToSet, Atlas atlas)
{
    var dynamicSkin = new Skin("dynamicSkin");
    var templateSkin = SpineInstance.Skeleton.Data.FindSkin("template");

    // Copy the template skin to the dynamic skin
    dynamicSkin.CopySkin(templateSkin);

    // Get the placeholder slot from the template skin
    var existingSlot = templateSkin.GetAttachment(placeholderName);
    if (existingSlot == null)
    {
        Console.WriteLine($"Slot {placeholderName} not found in template skin.");
        return;
    }

    var existingRegion = existingSlot as RegionAttachment;
    if (existingRegion == null)
    {
        Console.WriteLine($"Attachment for slot {placeholderName} is not a RegionAttachment.");
        return;
    }

    // Create a new region attachment
    var dynamicAttachment = new RegionAttachment(existingRegion.Name);

    // Get dynamic region data from the atlas and set the texture on our dynamic region
    var regionData = atlas.FindRegion(atlasTextureToSet);
    if (regionData == null)
    {
        Console.WriteLine($"Region {atlasTextureToSet} not found in atlas.");
        return;
    }

    dynamicAttachment.SetRegion(regionData);

    // Set the attachment on the dynamic skin
    dynamicSkin.SetAttachment(existingSlot.SlotIndex, placeholderName, dynamicAttachment);

    // Apply the dynamic skin to the skeleton
    SpineInstance.Skeleton.SetSkin(dynamicSkin);
    SpineInstance.Skeleton.SetSlotsToSetupPose();
    SpineInstance.AnimationState.Apply(SpineInstance.Skeleton);
}

Key Points:

  1. Copy the Template Skin: Start by copying the template skin to ensure all existing attachments are preserved.
  2. Find and Validate the Existing Slot and Attachment: Ensure the slot and attachment exist and are of the correct type.
  3. Create and Set the New Region Attachment: Create a new RegionAttachment and set its region using the atlas data.
  4. Apply the Dynamic Skin: Set the new skin on the skeleton and update the skeleton's pose and animation state.

Additional Considerations:

  • Ensure the Atlas is Correctly Loaded: Make sure the atlas is correctly loaded and contains the regions you want to use.
  • Update the Skeleton's Pose: After setting the new skin, update the skeleton's pose to reflect the changes.
  • Apply the Animation State: Apply the animation state to ensure the skeleton is rendered with the new attachments.

This approach should help you dynamically set textures on your Spine skeleton at runtime more effectively.

In your example method you call templateSkin.GetAttachment(placeholderName); but there is no overload for GetAttachment that takes only a name. The method requires a slot index as well.

I think the problem is that the UVs aren't being set properly in the dynamic region.

If I copy an existing region and set the atlas data, it sets the region data but the UV properties do not change and the template texture renders. If I create a brand new region without copying and set the atlas data, the UV array is all zeroes and the texture doesn't render.

So setting the region data doesn't affect the UVs. My next step will be to investigate how the UVs get set when loading normal, static skins.

Okay, I figured it out. First of all, in my project, I do not have to make the calls to SetSlotsToSetupPose and Apply that the bot suggested.

The key part that was missing was calling UpdateRegion() on the dynamic RegionAttachment after setting the texture region from the atlas. That forces the UVs to be recalculated and results in the dynamic texture properly rendering. Here's the key part of my method that needed to change:

// get dynamic region data from the atlas and set the texture on our dynamic region
var regionData = atlas.FindRegion(atlasTextureToSet);
dynamicRegion.Region = regionData;
dynamicRegion.UpdateRegion();

@profexorgeek Glad to hear you've figured it out, Sorry to hear that Spinebot's answer wasn't immediately helpful!

You might want to check out the Runtime API Reference page where the Runtimes Guide is missing to explain certain use-cases explicitly.

In case you are familiar with using Unity, you could also have a look at the spine-unity runtime examples, which cover a larger area of topics.

I do not have to make the calls to SetSlotsToSetupPose and Apply that the bot suggested.

Spinebot is correct in suggesting these calls. The calls to SetSlotsToSetupPose() and AnimationState.Apply(skeleton) might seem unnecessary, however as soon as there are animations which change the active attachments or colors (and alpha) at slots, missing to call them will lead to leftover state and incorrect display of your skins.