I'm triggering several animations inside CCCallBlock. Each animation inside has a duration but the number of them is defined in runtime. In the game there's state machine and I need to wait until the last animation is finished, but I can't use this:
if([self numberOfRunningActions] > 0)
because the action is a CCCallBlock with duration 0.
How I can set a duration for the CCCallBlock action? Is possible to do it in runtime?
It might be wrong for a lot of reasons, but I came up with this:
[CCCallBlock actionWithBlock:^{
float duration = [self.cardsMatrix peepCards];
[self runAction:[CCDelayTime actionWithDuration:duration]];
}];
here self is the CCLayer with the state machine that controls the game.
CCCallBlock runs a block which is a C function (sort of). You can't set a duration for a function.
You can add another CCCallBlock to your animation, put them in a CCSequence and when this CCCallBlock runs, you know that the animation is done.
[self runAction:[CCSequence actions:[CCCallBlock actionWithBlock:^{
NSLog(#"my block");
}], [my next function], nil]];
Related
I am trying to take a particle effect in my Cocos2d project. Particle effect shows good. But I am confused when I put the particle showing function into a thread as follows, it only shows tiny dark squares instead of the right texture. thanx in advance.
// thread caller
[self performSelectorInBackground:#selector(showParticleThrd:) withObject:nil];
// it works good when i call it directly
-(void) showParticleThrd{
CCParticleSystem * system = [[ParticleEffectSelfMade alloc] initWithTotalParticles:1];
[aLayer removeChildByTag:R_TAG cleanup:YES];
system.position = ccp(self.position.x,self.position.y);
[self.parent addChild:system z:R_ORDER tag:R_TAG];
[system release];
}
You can not modify anything cocos2d related in a background thread. Cocos2d requires you to make changes to nodes in the main thread, where the OpenGL context was created.
I have a ship and I want to make it to shoot every 1 sec. So here's what I did,
#property (nonatomic, assign) float lastDamageTime;
and in the update method,
if (CACurrentMediaTime - self.lastDamageTime > 1)
{
self.lastDamageTime = CACurrentMediaTime;
[self shoot];
}
But the problem is, say I pause the game right after the ship shoot a bullet, and then 1 sec later, I resume the game, the if statement pass the check, and the ship will shoot another bullet immediately. And that's not what I want.
So, what can I do to make sure the ship fire bullets each sec weather I pause the game or not? Should I use CCTimer instead?
Thank you in advance for your answer.
You could try and schedule your shoots as a CCRepeatForever action, so that cocos2d deals with pause for you. It'd look something like this (assuming you're inside a custom ShipSprite).-
CCDelayTime *delay = [CCDelayTime actionWithDuration:1.0];
CCCallFunc *funcShoot = [CCCallFunc actionWithTarget:self selector:#selector(shoot)];
CCRepeatForever *repeat = [CCRepeatForever actionWithAction:[CCSequence actionOne:delay two:funcShoot]];
[self runAction:repeat];
Instead of CCRepeatForever you can try something like this
[self schedule:#selector(shoot) interval:1];
In your pause code unschedule the shooting
[self unschedule:#selector(shoot)];
In your code where it detects an enemy call
[self unschedule:#selector(shoot)];
[self performSelector:#selector(shoot)];
[self schedule:#selector(shoot) interval:1];
And it will shoot immediately, however it will only shoot once and then restart at 1 second intervals etc... so if you need to change that you can just adjust the timings
Currently, I have a game where an object determines a point to go to. It then calculates a path to that point and constructs one long CCMoveTo animation to get to that point. With this method, the animation seems very smooth and continuous.
I am now trying to break this one long CCMoveTo to multiple CCMoveTo leveraging the update method that gets called periodically. I want to do this because at each node of the path that the objects passes through, there might be a distraction and I want my object to be able to act on that. So this is what I am doing:
- (void) update: (ccTime) dt
{
if(![self isWalking]){
CGPoint nextNode = [_path objectAtIndex:(_currentPathIndex%[_path count])];
_currentPathIndex++;
NSMutableArray *actions = [[NSMutableArray alloc] init];
[actions addObject:[CCCallBlockO actionWithBlock:^void(id obj) {
[(Monkey *) obj setIsWalking:NO];
} object:self]];
[self moveTo: nextNode withCallbacks: actions];
}
}
Note: that I set isWalking to NO as a callback when the object has completely reached the destination node. This will let it calculate the next node to go to and construct that animation. Without this, the object would try to runAction in the middle of an ongoing CCMoveTo action. The problem with this method is that the movement does not seem smooth and continuous anymore. There seems to be a lag at the end of each CCMoveTo animation
Anybody has any clue on how to fix this?
That's a side effect of cocos2d's CCAction system respectively the CCScheduler.
There will always be a 1-frame delay because when an action stops, it won't do any work in the current frame: it made the last position update in the previous frame, and in the current frame it does no longer exist as a running action.
If you now run another move action in a scheduled method, that action won't begin updating until the next frame because it will schedule itself to receive updates. And updates that are scheduled while cocos2d's CCScheduler performs updates will not be run until the next frame, due to the fact that you can't modify an array during enumeration.
My advise is always to avoid using CCMove* actions for gameplay purposes, and instead manually update position of game objects. It's easy enough to do, and if you need a code example, look inside the CCMoveTo class.
A workaround would be to extend the distance of the CCMoveTo and replace the action shortly before it completes. Though that'll be a hack and may actually be harder to implement correctly than manual position updates.
PS: That's an issue I have addressed in the action model of KoboldTouch. It implements its own action system, with more lightweight and reusable actions.
I have this setup:
A layer has a sprite as a child. The sprite has this code in its init method:
id fadeOut = [CCFadeOut actionWithDuration:1.0f];
id death = [CCCallFunc actionWithTarget:self selector:#selector(die)];
self.deathAction = [CCSequence actions:fadeOut, death, nil];
[self runAction:deathAction_];
The death action calls the sprite's 'die' method in which its status is changed to 'dead'.
On its update method the layer checks all children and removes the ones wich are 'dead' with:
[self removeChild:child cleanup:YES];
The problem is that the child sprite still has retaincount of 2 after this line. As I understand it is kept by CCCallFunc. If I omit this deathAction and instead remove the sprites that have zero opacity (when they are faded), the code is working and the dealloc method gets called.
How should I remove the sprite by using an action properly?
You don't show all of your code. But it seems you are keeping a reference to the action and your likely forgetting to release that.
retainCount is notoriously unreliable : link so please don't use it and certainly don't count on it to be accurate.
I am doing a game like chess. As soon my player move is completed(if he starts move from one place to another), my AI move is started (before my player reaching his destination ). Sometimes i find difficult which AI coin is moved now. how to delay it.
If your player movement is bounded by Core Animation, you can setup the setAnimationDidStopSelector to a custom STOP function and starts your AI there.
If you have a game loop with states, just add enough states (e.g. user_move_began, user_move_ended, ai_think_began, ai_think_ended, ai_move_began .. ) to sequence the flow.
I think I understand what you are saying now.
You need to do a CCSequence, with your AI function call in a CCCallFunc at the end:
CCSequence *playerMove = [CCSequence actions:
[CCMoveTo actionWithDuration: 0.4f position: CGPointMake(10,10)],
[CCCallFunc actionWithTarget: self selector: #selector(doAIstuff)],
nil];
[playerSprite runAction: playerMove];
You need to have your AI be a callable function (i.e. doAIstuff). It is confusing to that you say the code is in draw().