cocos2D: Adding delay after rendering a scene - cocos2d-iphone

I am trying to launch a physics object after loading a game scene, something similar to https://www.makegameswith.us/tutorials/getting-started-with-spritebuilder/creating-two-levels/
The code I have is something like this
- (void)didLoadFromCCB {
// tell this scene to accept touches
self.userInteractionEnabled = TRUE;
[self launchObject];
}
- (void) launchObject {
CCNode* object = [CCBReader load:#"Object"];
// Apply force to it
}
The problem is if I add a sleep method in didLoadFromCCB before launching the object or the first line in the launchObject, the game scene itself is loading only after that many seconds (say after clicking play) and immediately launches, but I want the game scene to load and after n seconds the physics object is launched.
I can easily solve this problem by using
- (void)update:(CCTime)delta
by setting some conditions for launch, but the question is, is that the right approach. I don't want to complicate update method to hold multiple if/else stuff and use it beyond what it's intended for, especially if there is another best way to do it.
I tried different solutions posted in this forum but didn't help my case.

Related

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

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);
}
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.
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.
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.

How to get control back after drawing on scene

I am trying to implement simple backtracking algorythm (solitaire game) and I also want to show the progress, not only the answer. So I decided to use QGraphicsView to draw gamefield's status on it. I want to update scene after I field's changd and when I want to get control back, to resume changing gamefield. What is the best way to do this?
UPD:
pseudocode
List<Step>* solve(GameField& field) {
//changing gamefield, clearing scene, adding some objects (rects with text) to scene
updateScene(); //here I want the scene to be updated right now, and
//not when it's scheduled to
}

Understanding cocos2d scenes, how they works really?

I'm stuck on something about the scenes creation and replacement in cocos2d, so I'm going to ask precisely what seems to be misunderstood by me. I have a game (fully working except for scene swapping, sadly) with some little-games, now, if I had to do this starting with a cocos2d scene as menu I wouldn't have any problem, but since I did it starting with UIKit I truly need to know better how the scenes are working to fix it.
Firstly, is it required to start a scene in the appDelegate? since I'm starting with UIkit and the scene must be shown after you choice the game (say, out of 3 choices), which scene should I put in the appDelegate? and where exactly? I'm putting the scene in this method:
-(void) directorDidReshapeProjection:(CCDirector*)director
{
if(director.runningScene == nil)
//start scene
}
If I put the FIRST scene, the UIKit part works good and when I start the "game number TWO" as first choice (say we play this game for first) I got the Open GL 0x0506 error, then the scene start.
If I put the first scene, I choice the first game, and then quit and choice the second game, the scene is replaced properly without that error.
If I put the first scene, and I start the "game number 1" it works (obviously) because he has the scene loaded, but I cannot know which game will start as first the user.
I tried with an "intro scene" loaded at the appDelegate but I got the same problem. the problem basically is "how to start scene if you have more than one scene and don't know which will be called as first"...
The 'getting started with iOS' documentation will really clear up a lot of these questions. You can find it at developer.apple.com - https://developer.apple.com/library/ios/#documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/Introduction/Introduction.html#//apple_ref/doc/uid/TP40007072
It explains just what an AppDelegate actually is, as well as how to use one properly. It is not immediately clear how to mix UIKit and cocos2d, but the above link cleared a lot up for me. Another very helpful resource is a tutorial by Ray Wenderlich -
http://www.raywenderlich.com/4817/how-to-integrate-cocos2d-and-uikit
From a bird's eye view, the CCDirector inherits from a UIWindow. Mixing UIKit and cocos2d is as simple as building your interface with UIKit, then at some point opening a UIWindow and allowing the CCDirector to start cocos2d. In a sense, the components act as almost two entirely separate entities.

How to add a short pause (think time) in a cocos2d game

I'm working on a two player iOS game that is similar to checkers. I'm using cocos2d to create this game.
I want a think time of .5 seconds between when the player's move renders and when the computer's move renders to simulate think time.
The flow of the game is controlled using NSNotification events and looks like this...
Player (computer or human) submits a move -> The board adds the new sprite -> The game controller updates the current player and asks them to submit a move.
I've tried adding usleep(500000) at the end of the board update or the beginning of the game update. What ends up happening is the sprites added in the board update, for the human player, don't show up until after the computer player has submitted his move. So the game waits 500 milliseconds and then updates with both moves.
Is there a way to force the CCLayer to update its child sprites before the usleep, or is there just a better way of adding this think time?
Thanks
if you schedule for receiving an update in your controller, you could slip time in the update:(ccTime)dt function.
in your .h
float _slipTime;
in your .m
// with other declaratives
static float THINK_TIME=.5f;
// last line before the stall
_slipTime=0.f;
[self schedule:#selector(pauseForThink:)];
-(void) pauseForThink:(ccTime) dt {
_slipTime+=dt;
if(_slipTime>THINK_TIME) {
[self unschedule:#selector(pauseForThink:)];
// trigger here whatever you wanted to accomplish after
// the think pause.
}
}
ok, this is simple, but will prevent the main loop from being blocked (this is what happens with your sleep). When running at 60 FPS, the pauseForThink method will be called about 30 times, and cocos will have 30 draw cycles during the pause.

How To Retrieve Actions From Sprite In cocos2d

I have a CCSprite that I'm using in a scene and have created multiple CCAnimation actions to apply to it all using a single CCSpriteFrameCache sharedSpriteFrameCache. While everything is working and I'm able to switch between animations, I feel like I'm doing poorly and would like to simplify my code by retrieving the running action(s) on the CCSprite to stop them individually before running the next action on it.
To help create some context, lets assume the following situation:
We have a CCSprite called mySprite
We have 3 separate CCAnimation actions defined for walking to the right, walking to the left, and sitting looking forward called: actionAnimWalkRight, actionAnimWalkLeft, and actionAnimSitForward respectively.
We want to have the sprite walk to the right when someone touches the screen right of mySprite, walk left when someone touches the screen left of mySprite and sit when someone touches mySprite.
The approach I'm using to accomplish this is as follows:
Place CCSprite as a child in the scene.
Tell the sprite to run an action using: [self runAction:actionWalkRight];
When I want to change the action after someone touches, I have a method called stopAllAnimationActions which I call before I apply a new action that stops any animation action no matter what's running. Basically lists ALL the CCAnimation/CCActions I have defined and stops each one individually since I don't want to use stopAllActions. as follows: [self stopAction:actionWalkRight]; [self stopAction:actionWalkLeft]; [self stopAction:actionSitForward];
Then I apply the new animation after the above method fires using: [self runAction:actionWalkLeft];
While this works, it just seems like a poor design to stop items that I know aren't running just because I don't know exactly what is running. So just looking for advice and the best recommended practice to do something like this within very complex situations so tracking every possible situation is difficult. Any feedback would be appreciated.
When creating the actions set the tag of that action with a constant:
actionWalkRight.tag= kCurrentAction;
[self runAction:actionWalkRight];
Then, retrieve the running action by that tag and stop it.
[self stopActionByTag:kCurrentAction];
I recommend you simplify your process and take advantage of the native Cocos features, including stopAllActions. Don't re-use actions, always create them from scratch as it has been well discussed among Cocos developers that re-using actions can be buggy.
Cocos is well optimized and features like stopAllActions are not performance hogs. It would probably be faster than your approach, actually.