Sheado

Hey All,

Is there a way to load skins in the cocos2d-x runtime using multiple atlases? I don't want to place all skins of a skeleton into a single atlas - I want to avoid the overhead of loading skins unused in specific areas of my game.

Here's what I'm doing right now:

CCSkeleton* skeletonNode = CCSkeleton::create("skeleton.json", "bird_all.atlas");
skeletonNode->setSkin( "bird_red" );
skeletonNode->setSlotsToBindPose();
skeletonNode->setAnimation("animation_test_0", true);
skeletonNode->timeScale = 0.5f;
skeletonNode->setPosition(ccp(windowSize.width / 3 * 2, windowSize.height / 2 ));
addChild(skeletonNode);

skeletonNode = CCSkeleton::create("skeleton.json", "bird_all.atlas");
skeletonNode->setSkin( "bird_orange" );
skeletonNode->setSlotsToBindPose();
skeletonNode->setAnimation("animation_test_0", true);
skeletonNode->timeScale = 0.5f;
skeletonNode->setPosition(ccp(windowSize.width / 3, windowSize.height / 2 ));
addChild(skeletonNode);
This works fine if the graphics for skins bird_red and bird_orange are all in one atlas file. If I try to create separate atlases (one for orange and one for red), then I get a segfault in spine::CCSkeleton::setSkin(char const*)

Is there a built-in way of handling this? If not, how should I go about making this happen? (I know AttachmentLoader was recommended for non-atlas loading here viewtopic.php?f=3&t=468&p=1902&hilit=skeleton+atlas#p1902 but I'm wondering if there's a better solution when dealing with atlases)

Thanks!
-Chad
Sheado
  • Сообщения: 66

Nate

Everything about a skeleton is loaded when the skeleton is loaded, that means all of its skins and all their attachments. By default the region for each region attachment is looked up when the attachment is loaded. You could change the attachment loader so it doesn't do this. You could then find the region for your attachments later, eg maybe right before they are drawn for the first time. You could also iterate the attachments in a skin and find the region for them. Which atlas you use to find them is up to you.

Sorry it isn't easier, but it is unusual to not want to find the regions at load time.

Note if you want to create multiple instances of the same skeleton, you should load a skeleton data and use that to create each CCSkeleton.
Аватара пользователя
Nate

Nate
  • Сообщения: 9845

Sheado

Thanks for the response Nate,
I'll look into modifying the attachment loader.

I'd like to yammer on though in case you or somebody else has a different suggestion for optimizing my issue:

Let's say my game has 10 levels. Each level has adds some new mammals - e.g. horses, dogs, pigs - all of which share the same skeleton and animation. With the current setup, a user playing level 1 would have to load an atlas for resources he/she will not see during the entire session they play. Same goes for level 10, where some of the earlier mammals are not needed. The result is slower load time and higher memory overhead for most of the levels.

The same can be thought of with the goblins example. If a user picks the girl goblin as their character, then the whole time they're playing the game, the resources for the boy goblin are sitting around in memory being unused.

Is there a best practice for dealing with this? I've thought of atlasing by level, but there is some reuse across different levels. So for me, ideally, it would be best to create an atlas for each skin rather than for each skeleton.

Thanks for reading this far =]
-Chad
Sheado
  • Сообщения: 66

Nate

If it all fits in memory and it all loads fast enough (both of which are extremely likely), then worrying about it is premature optimization. The user will never notice the difference. Loading 100ms faster isn't worth anything.

That said, if you still want to go on optimizing, it would be best to have an atlas per level. This minimizes texture binds. You may not be able to of course, eg if the user can pick girl or boy goblin, those are likely separate from the level atlas. You can still have 1 atlas per level + 1 atlas per player character. What you want to avoid is multiple atlases per level where you switch back and forth between textures for many draw calls. 30 or so texture binds per frame starts to hurt the framerate. You could also build up an atlas at runtime with only the stuff you need. Eg, for libgdx see PixmapPacker.

Again, unless you are loading 16+ mb of uncompressed image data, don't worry about it. Focus on things the user will actually notice (and on finishing your game).
Аватара пользователя
Nate

Nate
  • Сообщения: 9845

Sheado

Thanks for the feedback Nate,

We're planning on having up to 100 different characters by the time we're done, with probably 30 sharing the same animations. To prevent memory overflow, we're probably going to limit the number of characters that appear on screen while cleaning up memory of those who have gone offscreen.

PixmapPacker sounds interesting - I'll check it out. Also, I'm still tempted to tinker with the attachment loader.

Btw, thanks for the note on loading skins from skeletondata. Here's my code in case anybody else needs it (cocos2d-x):

...
Atlas* atlas = Atlas_readAtlasFile("bird_all.atlas");
SkeletonJson* skeletonJson = SkeletonJson_create( atlas );
SkeletonData* skeletonData = SkeletonJson_readSkeletonDataFile(skeletonJson, "skeleton.json");
AnimationStateData* animationStateData = AnimationStateData_create( skeletonData );
CCSkeleton* skeletonNode = CCSkeleton::create(skeletonData, animationStateData );
skeletonNode->setSkin( "bird_red" );
...
Sheado
  • Сообщения: 66

Sheado

Hi Nate,

I don't know if you're still reading this post.. I made some progress on this - some more feedback is always appreciated =]
I made this small change to allow skeletons to load with partial atlases - 'git diff' output:
diff --git a/spine-c/src/spine/SkeletonJson.c b/spine-c/src/spine/SkeletonJson.c
index 242b74d..2a47ee4 100644
--- a/spine-c/src/spine/SkeletonJson.c
+++ b/spine-c/src/spine/SkeletonJson.c
@@ -351,11 +351,12 @@ SkeletonData* SkeletonJson_readSkeletonData (SkeletonJson* self, const char* jso

Attachment* attachment = AttachmentLoader_newAttachment(self->attachmentLoader, type, attachmentName);
if (!attachment) {
- if (self->attachmentLoader->error1) {
- SkeletonData_dispose(skeletonData);
- _SkeletonJson_setError(self, root, self->attachmentLoader->error1, self->attachmentLoader->error2);
- return 0;
- }
+// NOTE: sheado - removed to allow partial atlas support:
+// if (self->attachmentLoader->error1) {
+// SkeletonData_dispose(skeletonData);
+// _SkeletonJson_setError(self, root, self->attachmentLoader->error1, self->attachmentLoader->error2);
+// return 0;
+// }
continue;
}
Being unfamiliar with the run-time code, I don't know if this could cause a bug elsewhere though. But it does allow me to do the following in cocos2d-x:
// load a character with a skeleton and a specific skin's atlas
atlas = Atlas_readAtlasFile("bird_orange.atlas");
skeletonJson = SkeletonJson_create( atlas );
skeletonData = SkeletonJson_readSkeletonDataFile(skeletonJson, "skeleton.json");
animationStateData = AnimationStateData_create( skeletonData );
skeletonNode = CCSkeleton::create(skeletonData, animationStateData );
skeletonNode->setSkin( "bird_orange" );
skeletonNode->setSlotsToBindPose();
skeletonNode->setAnimation("animation_test_0", true);
skeletonNode->timeScale = 0.8f;
skeletonNode->setPosition(ccp(windowSize.width / 3 * 2, parentNode->getContentSize().height/2 + i*5));
parentNode->addChild(skeletonNode);

// load another cskeleton with a different skin atlas
atlas = Atlas_readAtlasFile("bird_red.atlas");
skeletonJson = SkeletonJson_create( atlas );
skeletonData = SkeletonJson_readSkeletonDataFile(skeletonJson, "skeleton.json");
animationStateData = AnimationStateData_create( skeletonData );
skeletonNode = CCSkeleton::create(skeletonData, animationStateData );
skeletonNode->setSkin( "bird_red" );
skeletonNode->setSlotsToBindPose();
skeletonNode->setAnimation("animation_test_0", true);
skeletonNode->timeScale = 0.8f;
skeletonNode->setPosition(ccp(windowSize.width / 3, parentNode->getContentSize().height/2 - 20 + i*5));
parentNode->addChild(skeletonNode);
In the code above, skeleton.json has skins bird_red and bird_orange. In the first block of code I use bird_orage.atlas which only has the bird_orange skin graphics. In the second block, I do the same but with a different atlas for a different skin.

The main disadvantage is that I have to keep reloading skeleton.json each time I need to load a character, but that seems to be ok so far. One unexpected advantage is that I got a 7fps boost (testing on an old Droid) with this approach.

Again, thanks for reading this far =]
Sheado
  • Сообщения: 66

Nate

You don't have to comment out things in spine-c. You can provide your own attachment loader using SkeletonJson_createWithLoader instead of SkeletonJson_create. Use this as a base for your own:
https://github.com/EsotericSoftware/spi ... ntLoader.c
To avoid loading the JSON many times, you can look up in one atlas, then another if not found. Or, I just added a Skin* parameter so you can decide which atlas to look up in using the skin name.

Note I did some refactoring this morning, so you'll want to update.
Аватара пользователя
Nate

Nate
  • Сообщения: 9845

Sheado

Thanks Nate,

That helped. I got the attachment loader working and all is peachy.
I didn't implement the JSON / skin-name optimization though - I think I'm going to wait and see if we need that optimization.

-Chad
Sheado
  • Сообщения: 66


Вернуться в Editor