• Runtimes
  • Load another asset (.atlas and .png)

Hello, I have a situation that should load another asset (.atlas and .png) from another file in my iOS project. I am using spine-c as my runtime. So how to make it work? Thank you!

Related Discussions
...

I'm afraid that's not enough information for us to help. If you already have code to load one atlas, loading another atlas works exactly the same, safe for the change in file name. Could you try to describe in more detail what you are trying to achieve?

badlogic wrote

I'm afraid that's not enough information for us to help. If you already have code to load one atlas, loading another atlas works exactly the same, safe for the change in file name. Could you try to describe in more detail what you are trying to achieve?

I am porting another app that also using spine in android. The android team already achieved that with

private fun applyItemSkin(skinInfo: Pair<String, SkinType>) {
    val skinName = skinInfo.first

Gdx.app.postRunnable {
    try {
        skinAtlas = TextureAtlas(Gdx.files.local(getAvatarPath("$skinName.atlas")))

        skeleton?.let {
            val newSkin = it.createItemSkin(skinAtlas, skinInfo.second, skinName)
            skeleton?.addSkin(newSkin)
        }

    } catch (e: FileNotFoundException) {
        e.printStackTrace()
        Log.e(tag<AvatarView>(), "Atlas for this skin was not found: $skinName")
    }
}
}

They create new skin from different atlas file, then create skin item with this

internal fun Skeleton.createItemSkin(newAtlas: TextureAtlas?, skinType: SkinType, newSkinName: String): Skin {
    if (newAtlas == null) {
        Log.e(TAG, "CreateItemSkin: newAtlas null.")
        return Skin("")
    }

val templateSkin = this.getSkin(skinType.name)
if (templateSkin == null) {
    Log.e(TAG, "CreateItemSkin: can not find template skin with itemName ${skinType.name}.")
    return Skin("")
}

val newSkin = Skin(newSkinName)

this.slots.forEachIndexed { index, slot ->
    val slotName: String? = slot.data.attachmentName
`
        slotName?.let {
            val templateAttachment = templateSkin.getAttachment(index, slotName)
            templateAttachment?.let {

            val newAttachmentName = slotName + "_" + newSkinName
            val newAttachment =
                templateAttachment.copyAttachment(newAtlas, newAttachmentName)
            newAttachment?.let { newSkin.addAttachment(index, slotName, newAttachment) }
        }
    }

}

if (skinType.name.endsWith(SHORT)) {
    val longSkinType = skinType.name.replace(
        SHORT,
        LONG
    )
    val longTemplateSkin: Skin? = this.getSkin(longSkinType)
    longTemplateSkin?.let {
        this.applyTransparentToSkinDiffs(it, newSkin)
    }
}

return newSkin
}

Basically, he add new skin from the new asset (png and atlas) then add it into the skeleton. Is there any tutorial how to do it in iOS using spine-c? Thank you!

I'm afraid there's no tutorial, but the Spine API is the same across programming languages! In your first code example, constructing the texture atlas is done via libGDX's API. What engine are you using? Cocos2d-x? In any case, our engine integrations offer simple APIs to load texture atlases through the engine itself. For Cocos2d-x you can find it here (since 3.7 we use spine-cpp not spine-c though!):

spine-runtimes/SkeletonRenderer.cpp at 3.8

Your second piece of Kotlin code should translate pretty much the same to spine-c or spine-cpp. Skin [url=http://esotericsoftware.com/spine-api-reference#Skin-getAttachment]getAttachment[/url]() becomes spSkin_getAttachment() or spine::Skin::getAttachment(), Attachment#copyAttachment() becomes spAttachment_copyAttachment() or spine::Attachment::copyAttachment() etc.

11 days later
badlogic wrote

I'm afraid there's no tutorial, but the Spine API is the same across programming languages! In your first code example, constructing the texture atlas is done via libGDX's API. What engine are you using? Cocos2d-x? In any case, our engine integrations offer simple APIs to load texture atlases through the engine itself. For Cocos2d-x you can find it here (since 3.7 we use spine-cpp not spine-c though!):

spine-runtimes/SkeletonRenderer.cpp at 3.8

Your second piece of Kotlin code should translate pretty much the same to spine-c or spine-cpp. Skin [url=http://esotericsoftware.com/spine-api-reference#Skin-getAttachment]getAttachment[/url]() becomes spSkin_getAttachment() or spine::Skin::getAttachment(), Attachment#copyAttachment() becomes spAttachment_copyAttachment() or spine::Attachment::copyAttachment() etc.

Finally I managed to create a skin from another asset (.atlas and .png), but still have an issue.

The ideas are:

  • Load the new atlas
  • Create a skin object that already exist in the skeletonData, named templateSkin
  • Create a new skin object with skin name, called newSkin
  • Get the attachment from templateSkin
  • Paste it into the new one newSkin
  • Apply the newSkin into our skeleton

Here's our code in Objective-C

- (spSkin*)createItemSkinWithAtlas:(NSString[/i])skinAtlas withCategory:(NSString*)category andSkinName:(NSString*)newSkinName {
    // Load the new atlas
    spAtlas *newAtlas = spAtlas_createFromFile([skinAtlas UTF8String], 0);
    
// Create a skin object that already exist in the skeletonData, named `templateSkin` spSkin *templateSkin = spSkeletonData_findSkin(_skeleton->data, "top_short_basic_m");
// Create a new skin object with skin name, called `newSkin` spSkin *newSkin = spSkin_create([newSkinName UTF8String]);
// Get the attachment from `templateSkin` for (int i=0; i<(self.skeleton->slotsCount - 1); i++) { const char *slotName = self.skeleton->data->slots[i]->attachmentName; spAttachment *templateAttachment = spSkin_getAttachment(templateSkin, i, slotName);
if (templateAttachment != NULL) { NSString *newAttachmentName = [[NSString alloc] initWithFormat:@"%s_%@", slotName, newSkinName]; spAttachment *newAttachment = [self copyAttachmentFrom:templateAttachment withAtlas:newAtlas newAttachmentName:newAttachmentName]; // Paste it into the new one `newSkin` if (!newAttachment) { spSkin_setAttachment(newSkin, i, slotName, newAttachment); } } }
return newSkin; }

Here is my copyAttachmentFrom function

// adding new function that copy attachment from existing atlas into the new one
- (spAttachment[i])copyAttachmentFrom:(spAttachment[/i])attachment withAtlas:(spAtlas*)atlas newAttachmentName:(NSString*)regionName {
    spAtlasRegion *searchedRegion = spAtlas_findRegion(atlas, [regionName UTF8String]);
    
if (attachment->type == SP_ATTACHMENT_REGION) { spRegionAttachment *newAttachment = (spRegionAttachment*)attachment; newAttachment->rendererObject = searchedRegion; spRegionAttachment_updateOffset(newAttachment); return (spAttachment*)newAttachment; } else if (attachment->type == SP_ATTACHMENT_MESH) { spMeshAttachment *newAttachment = (spMeshAttachment*)attachment; newAttachment->rendererObject = searchedRegion; spMeshAttachment_updateUVs(newAttachment); return (spAttachment*)newAttachment; } else { // just following android's code, only support 2 types return attachment; } }

Then I create a skin with:

spSkin *theNewSkin = [self createItemSkinWithAtlas:@"animalbluetshirt12_m.atlas" withCategory:@"top_short_basic_m" andSkinName:@"animalbluetshirt12_m"];
spSkeleton_setSkin(_skeleton, theNewSkin);
spSkeleton_setSlotsToSetupPose(_skeleton);

But sadly, my theNewSkin didnt' showed up in my character. I think, the copy attachment from existing skin into the new one was incomplete. Did I missing a step here?

Thank you in advance!

In copyAttachmentFrom() you are not actually making a copy. You are just casting the input attachment to a specific attachment type and return it. If you look into the original Kotlin code, there's a call to copyAttachment(). You will need to do the same on the attachments in Objective-C, using spAttachment_copy().

Besides that, make sure that searchedRegion is actually not null through debugging. That'd also explain why nothing shows up.

badlogic wrote

In copyAttachmentFrom() you are not actually making a copy. You are just casting the input attachment to a specific attachment type and return it. If you look into the original Kotlin code, there's a call to copyAttachment(). You will need to do the same on the attachments in Objective-C, using spAttachment_copy().

Besides that, make sure that searchedRegion is actually not null through debugging. That'd also explain why nothing shows up.

In the Kotlin's code, copyAttachment() is an extension from Attachment, here's the code

/**
 * Create an attachment by finding a region in an [atlas] with the itemName [regionName]
 * and copying that region into receiver object (that is a template attachment) then returning it.
 *
 */
internal fun Attachment.copyAttachment(atlas: TextureAtlas, regionName: String): Attachment? {
    val searchedRegion: TextureAtlas.AtlasRegion = atlas.findRegion(regionName) ?: return null

    if (this is RegionAttachment) {
        this.apply {
            region = searchedRegion
            updateOffset()
        }
    } else if (this is MeshAttachment) {
        this.apply {
            region = searchedRegion
            updateUVs()
        }
    }
    return this
}

From that function, I was trying to translate into Objective C,

// adding new function that copy attachment from existing atlas into the new one
- (spAttachment[i])copyAttachmentFrom:(spAttachment[/i])attachment withAtlas:(spAtlas*)atlas newAttachmentName:(NSString*)regionName {
    spAtlasRegion *searchedRegion = spAtlas_findRegion(atlas, [regionName UTF8String]);
    
if (attachment->type == SP_ATTACHMENT_REGION) { spRegionAttachment *newAttachment = (spRegionAttachment*)spAttachment_copy(attachment); newAttachment->rendererObject = searchedRegion; spRegionAttachment_updateOffset(newAttachment); return (spAttachment*)newAttachment; } else if (attachment->type == SP_ATTACHMENT_MESH) { spMeshAttachment *newAttachment = (spMeshAttachment*)spAttachment_copy(attachment); newAttachment->rendererObject = searchedRegion; spMeshAttachment_updateUVs(newAttachment); return (spAttachment*)newAttachment; } else { return spAttachment_copy(attachment); } }

But still showing nothing. Is there any a step that I missed? Thank you.

I'm afraid I can't help just by looking at a few code snippets. There are many things that can go wrong resulting in nothing showing up. The code you posted so far looks OK to me.

If you create a super minimal Xcode project that let's me easily reproduce the issue, I can take a look and try to fix it. Something that loads a skeleton, does the replacement with your code, and draws the skeleton would do. Please don't send your full blown project 🙂 You can send (confidential) files to contact@esotericsoftware.com

badlogic wrote

I'm afraid I can't help just by looking at a few code snippets. There are many things that can go wrong resulting in nothing showing up. The code you posted so far looks OK to me.

If you create a super minimal Xcode project that let's me easily reproduce the issue, I can take a look and try to fix it. Something that loads a skeleton, does the replacement with your code, and draws the skeleton would do. Please don't send your full blown project 🙂 You can send (confidential) files to contact@esotericsoftware.com

I made a progress. For an attachment with SP_ATTACHMENT_REGION type, it worked well. But it doesn't work the SP_ATTACHMENT_MESH type. There is a missplaced image region (not placed correctly)

Here's my mini project in GitHub https://github.com/MountMaster/spine-c

I created a custom initializer for SkeletonRenderer class, but in this project, I just edit and add some action (create some skins, and apply them into our skeleton) here https://github.com/MountMaster/spine-c/blob/2d27f0fd6ae8cd4ee23b987d1670e1323fd23dc0/src/spine/SkeletonRenderer.m#L66

I created few skin from another atlases here
https://github.com/MountMaster/spine-c/blob/2d27f0fd6ae8cd4ee23b987d1670e1323fd23dc0/src/spine/SkeletonRenderer.m#L120

I made a function that will copy an attachment from existing skin
https://github.com/MountMaster/spine-c/blob/2d27f0fd6ae8cd4ee23b987d1670e1323fd23dc0/src/spine/SkeletonRenderer.m#L148

Thank you!

I've spend over an hour trying to get your project to compile and run. I managed to get it to compile. But the main scene (SpineboyExample.cpp) doesn't even call into your modified code. Soooo, could you please update your project that it:

  1. Compiles out of the box
  2. Actually sets up a scene that calls into your code for loading and rendering