How do you load a scene while animating a sprite in cocos2d-x?
Asked Answered
V

3

15

I have a "stage selection" scene and a "game" scene. However when the user presses the button to start the game scene there is a delay between the pressing and the scene showing (about 2 seconds or more on older devices). So what i thought is i should create a loading scene.

So what i am doing right now is passing to my "Loading" scene a std::function which gets called 0.1 seconds after the loading scene appears. This function has the code to start the "game" scene like this:

For creating the loading scene.

auto loading_scene = LoadingScene::createLoadingScene([stage_course]() {

    Director::getInstance()->replaceScene(Game::createScene(stage_course->course_id));

});

Director::getInstance()->replaceScene(loading_scene);

To load the game scene.

void LoadingScene::onEnter()
{
    Node::onEnter();

    call_after(0.1, callback_start);  
}

The result of this is the loading scene showing with a simple animated sprite of a character running. At first i tried with a delay of 1.0 seconds before the callback to check that the sprite is working correctly (it does, the character runs). But it stops moving when the callback is executed (the one that loads the new scene) and remains like this for about 1-2 seconds until the scene has loaded and then its presented..

Does anyone know how to keep the sprite animated while the scene is getting load so that it never stops running until the "game" scene is shown?


Edit:

I am using cocos2d-x-3.8.

My loading scene has the following code in its init function to create an animation which is used to animate a spire:

// Create the sprite animation
Animation *animation = Animation::create();
for (int i = 0; i < INT16_MAX; i++)
{
    string frame_sprite_name = StringUtils::format("Interface/loading/0_%d.png",i);

    auto frame = SpriteFrameCache::getInstance()->getSpriteFrameByName(frame_sprite_name);

    if (frame) {
        animation->addSpriteFrame(frame);
    } else {
        break;
    }
}

animation->setDelayPerUnit(0.15f);


Animate *animate = Animate::create(animation);


// Create a temporal sprite to run the animation

auto temp_sprite = Sprite::create();

temp_sprite->setAnchorPoint(Vec2(0.5,0.5));
temp_sprite->setPosition(Vec2(DISPLAY_WIDTH/2.0f,DISPLAY_HEIGHT/2.0f));


this->addChild(temp_sprite);

temp_sprite->runAction(RepeatForever::create(animate));

Edit 2:

The reason why my game scene takes too much time to load is because i am loading all the spritemaps my stage needs like this:

// Shared

if (!SpriteFrameCache::getInstance()->isSpriteFramesWithFileLoaded(STUDENTS_SPRITE_MAP)) {
    SpriteFrameCache::getInstance()->addSpriteFramesWithFile(STUDENTS_SPRITE_MAP);
}

if (!SpriteFrameCache::getInstance()->isSpriteFramesWithFileLoaded(OTHERS_SPRITE_MAP)) {
    SpriteFrameCache::getInstance()->addSpriteFramesWithFile(OTHERS_SPRITE_MAP);
}

if (!SpriteFrameCache::getInstance()->isSpriteFramesWithFileLoaded(INTERFACE_SPRITE_MAP)) {
    SpriteFrameCache::getInstance()->addSpriteFramesWithFile(INTERFACE_SPRITE_MAP);
}

if (!SpriteFrameCache::getInstance()->isSpriteFramesWithFileLoaded(ZOMBIES_SPRITE_MAP)) {
    SpriteFrameCache::getInstance()->addSpriteFramesWithFile(ZOMBIES_SPRITE_MAP);
}

if (!SpriteFrameCache::getInstance()->isSpriteFramesWithFileLoaded(PORTRAITS_SPRITE_MAP)) {
    SpriteFrameCache::getInstance()->addSpriteFramesWithFile(PORTRAITS_SPRITE_MAP);
}

if (!SpriteFrameCache::getInstance()->isSpriteFramesWithFileLoaded(CUTS_SPRITE_MAP)) {
    SpriteFrameCache::getInstance()->addSpriteFramesWithFile(CUTS_SPRITE_MAP);
}

// Exclusive

if (!SpriteFrameCache::getInstance()->isSpriteFramesWithFileLoaded(TEACHERS_SPRITE_MAP)) {
    SpriteFrameCache::getInstance()->addSpriteFramesWithFile(TEACHERS_SPRITE_MAP);
}
Vashtivashtia answered 14/9, 2015 at 4:42 Comment(0)
M
5

Try this:

void HelloWorld::loadTextures1(){
    Director::getInstance()->getTextureCache()->addImageAsync("sprites1.png", CC_CALLBACK_1(HelloWorld::loadTextures2, this));
}

void HelloWorld::loadTextures2(Texture2D* texture1){
    this->texture1 = texture1;
    CCLOG("Texture 1 loaded!");
    Director::getInstance()->getTextureCache()->addImageAsync("sprites2.png", CC_CALLBACK_1(HelloWorld::loadTextures3, this));
}


void HelloWorld::loadTextures3(Texture2D* texture2){
    this->texture2 = texture2;
    CCLOG("Texture 2 loaded!");
    Director::getInstance()->getTextureCache()->addImageAsync("sprites3.png", CC_CALLBACK_1(HelloWorld::allTexturesLoaded, this));
}

void HelloWorld::allTexturesLoaded(Texture2D* texture3){
    this->texture3 = texture3;
    CCLOG("Texture 3 loaded!");

    auto cache = SpriteFrameCache::getInstance();

    cache->addSpriteFramesWithFile("sprites1.plist", texture1);
    cache->addSpriteFramesWithFile("sprites2.plist", texture2);
    cache->addSpriteFramesWithFile("sprites3.plist", texture3);

    auto scene = GameScene::createScene();
    Director::getInstance()->replaceScene(TransitionShrinkGrow::create(.5, scene));
}

It asynchronously loads these 3 textures and then synchronously load plist files with preloaded textures. There's a bit of freeze on the end (synchronous part), but it's not bad I think. You may also dig deeper into SpriteFrameCache and try to optimise it.

Musso answered 21/9, 2015 at 8:34 Comment(1)
I guess this is the best approach, its kind of what the other answers say but easier to understand thanks to the example code. I guess i can make the info about what sprite sheets are needed in each scene static per scene type and recursively load all needed based on it. Then i can use this for most of my scenes. thanks. (i didn't know i could load the texture and then associate it with the spriteframe cache like this. I thought the 'addSpriteFramesWithFile' would recreate everything from scratch so i didn't thought i could use the addImageAsync method.Vashtivashtia
D
1

I am not aware that cocos2d-x supports this particularly well:

CSLoader loads the scene on the current thread so it blocks the animation loop until its finished.

To achieve smooth animations wile CSLoader is working it would need to be refactored either * to run in a worker thread - Which is going to be hard as (last time I looked) any Ref derived object is going to panic if its not created on the main cocos thread. * to frequently callout to the Dispatcher / runloop to allow a frame to animate. I havn't looked at the cocostudio::CSLoader code to see how amenable it would be to this... it certainly doesn't seem to support this out the box.

The alternative that might be hard to achieve is to simply break you scene up into chunks - each of which can be quickly loaded so there is no discernible load delay.

Detwiler answered 14/9, 2015 at 7:35 Comment(5)
What do you mean by breaking the scene into chunks?Vashtivashtia
Hopefully the entire scene is not visible all at once. Load the initially invisible bits later, when they are needed.Detwiler
i have exactly the same problem :( the only way i found is load sprites manually withoud using cocos studio CSLoader, calling : auto TexureCache = Director::getInstance()->getTextureCache(); TexureCache->addImageAsync("MainMenuScreen/Background.png", CC_CALLBACK_1(GS_Loading::LoadingCallback, this));Baccarat
@Baccarat im confused, i don't use cocos studio.Vashtivashtia
Ah. It initially sounded like you were.Detwiler
T
1

I agree with what Chris is saying, specifically off-loading work or breaking things into chunks.

True, Cocos2d-x is not designed for multi-threading, so that's why you need to implement an update callback that does only a small amount of work, then waits till the next runloop to allow the engine to render.

I wish I can help you solve the loading issues, but the code you are showing is only for loading the animations, which isn't really the problem. We need to see the code that is causing the slow down to provide some practical solutions.

Titanothere answered 16/9, 2015 at 10:17 Comment(2)
I think the slowdown in the game scene comes from the fact that i am loading 6 or 7 sprite maps. But i don't think i can choose not to load some of them since im using them all, and i don't want to have slowdowns while the user is playing. Was really hoping someone knew how or had come with a way to do this. Something that comes to mind is load the spritemaps asynchronously on the loading scene but i dunno if that is possible. I do remember seeing that textures could be loaded asynchronously but dunno about spritemaps.Vashtivashtia
@Chiquis I never said load some of them and start the scene. You should load part of the data, and wait for the next runloop to continue loading. Once you load everything, then you transition to the actual scene. Again, it is important here you know for a fact what's taking time, and we need to look at that code to suggest possible solutions.Titanothere

© 2022 - 2024 — McMap. All rights reserved.