I'm making an iOS game and part of it includes a marble that is randomly spawned and moves along a path and disappears upon completion of said path. Here is what I am using to spawn and move the marble:
-(void) createMarbleObstacle :(CCTime) randomTimeInterval{
marbleObj = (Marble *)[CCBReader load: #"Marble"];
marbleObj.position= _cLnode.position;
[_contentNode addChild: marbleObj];
[marbleObj moveMarble:(randomTimeInterval*5) :_bLnode: _aLnode];
}
-(void) moveMarble: (CCTime)interval :(CCNode*)a :(CCNode*)b{
moveM1 = [CCActionMoveTo actionWithDuration:(interval) position: a.position];
moveM2 = [CCActionMoveTo actionWithDuration:(interval) position: b.position];
moveM4 = [CCActionSequence actions:moveM1, moveM2, nil];
[self runAction: moveM4];
}
However, I can't figure out how I would go about making the marble disappear upon completion of the CCActionSequence. Thanks for the help.
add yet another action to your sequence in moveMarble:
id clean = [CCActionCallBlock actionWithBlock:^{
[_contentNode removeChild:marbleObj cleanup:YES];
}];
moveM4 = [CCActionSequence actions:moveM1, moveM2, clean, nil];
or something like that :)
Related
I've a player of Sprite * type in cocos2dx V3, I want it to run different animation on different time interval, I could not find method to pause and then resume a specific animation(Action). Although I can Pause and Resume all actions of Sprite Simultaneously using _player->pauseSchedulerAndActions().I'm using "CCRepeatForever" actions on sprite, so, I must have to pause one to resume other.Please help to Pause an action by tag or by any other method.
Thanks In Advance.
Oops
I made the assumption that this was Objective-C but #Droppy has informed me that it is not.
I didn't realise cocos2d-x was different. However, because this is a fairly high level framework the concept behind what I've done in the answer will still work. I'll keep the answer here for now.
The answer
It's been a while since I've done any Cocos2D stuff but I can give you the idea.
Instead of creating an action and repeating it forever you should have a method something like this...
- (void)twirlAround
{
// only create and perform the actions if variable set to YES
if (self.twirling) {
// this will do your action once.
CCAction *twirlAction = // create your twirl action (or whatever it is)
// this will run this function again
CCAction *repeatAction = [CCActionCallBlock actionWithBlock:^{
[self twirlAround];
}];
// put the action and method call in sequence.
CCActionSequence *sequence = [CCActionSequence actions:#[twirlAction, repeatAction]];
[self runAction:sequence];
}
}
This will run repeatedly as long as the twirling property is set to YES.
So, somewhere else in your code (probably where you are currently adding your repeating action) you can do this...
self.twirling = YES;
[self twirlAround];
This will start the repeated twirling.
To stop it you can then do...
self.twirling = NO;
This will stop the twirling.
Alternative method
- (void)twirlAround
{
// this will do your action once.
CCAction *twirlAction = // create your twirl action (or whatever it is)
// this will run this function again
CCAction *repeatAction = [CCActionCallBlock actionWithBlock:^{
if (self.twirling) {
[self twirlAround];
}
}];
// put the action and method call in sequence.
CCActionSequence *sequence = [CCActionSequence actions:#[twirlAction, repeatAction]];
[self runAction:sequence];
}
based on Fogmeister advice, this is cocos2d-x version of that
void MySprite::jumpForever(){
if (!twirling) return;
auto jump = JumpBy::create(0.5, Vec2(0, 0), 100, 1);
auto endCallback = CallFuncN::create(CC_CALLBACK_1(MySprite::jumpForever,this));
auto seq = Sequence::create(jump, endCallback, nullptr);
runAction(seq);
}
i don't completely understand the best choice in sprite kit animation;
1) Apple in "Adventure" example use this methodology, they store in memory animation as pictures in nsarray :
static NSArray *sSharedTouchAnimationFrames = nil;
- (NSArray *)touchAnimationFrames {
return sSharedTouchAnimationFrames;
}
- (void)runAnimation
{
if (self.isAnimated) {
[self resolveRequestedAnimation];
}
}
- (void)resolveRequestedAnimation
{
/* Determine the animation we want to play. */
NSString *animationKey = nil;
NSArray *animationFrames = nil;
VZAnimationState animationState = self.requestedAnimation;
switch (animationState) {
default:
case VZAnimationStateTouch:
animationKey = #"anim_touch";
animationFrames = [self touchAnimationFrames];
break;
case VZAnimationStateUntouch:
animationKey = #"anim_untouch";
animationFrames = [self untouchAnimationFrames];
break;
}
if (animationKey) {
[self fireAnimationForState:animationState usingTextures:animationFrames withKey:animationKey];
}
self.requestedAnimation = VZAnimationStateIdle;
}
- (void)fireAnimationForState:(VZAnimationState)animationState usingTextures:(NSArray *)frames withKey:(NSString *)key
{
SKAction *animAction = [self actionForKey:key];
if (animAction || [frames count] < 1) {
return; /* we already have a running animation or there aren't any frames to animate */
}
[self runAction:[SKAction sequence:#[
[SKAction animateWithTextures:frames timePerFrame:self.animationSpeed resize:YES restore:NO],
/* [SKAction runBlock:^{
[self animationHasCompleted:animationState];
}]*/]] withKey:key];
}
I appreciate this methodology, but i can't understand. Is storing SKAction in memory not better choice and use animation always like this ?
[self runAction:action];
without making always new SKAction;
[SKAction animateWithTextures:frames timePerFrame:self.animationSpeed resize:YES restore:NO]
Storing SKTextures in an NSArray is the recommended methodology to be used for animation. I can't say that I found SpriteKit's documentation to be lacking on the subject either, as SKAction has the method animateWithTextures.
You could indeed have a SKAction that has a given animation defined by a given NSArray, and maybe store those in a NSMutableDictionary with an animation name for a key. However this methodology I see above just has the animateTextures line of code once in their fireAnimationState method, and you can pass parameters to it such as the animationState and a key.
I think you would need to take more than a surface look at their methodology to determine why they chose to go this route. You can see that the animationState was being utilized upon completion of the animation and likely triggered something else.
[self runAction:action] is indeed simple, however it's also easy to see that it's not in any way managing an animationState or key, which I have to assume they decided their game needed to do.
Also keep in mind that their methodology likely has a higher level where in a given player class it's calling a flexible method for enacting an animation for a given game entity and doing other things besides just changing the animation. So in their high level coding they might be doing something more like this :
[self runAnimation:#"jump"];
or even
[self jump];
And because they have designed a low level system that manages animation states and adds keys for a given management design, they don't ever have to code the long line that you are pointing out except once in that method you see above.
A few reasons I have found it useful to store the NSArray of frames and not storing wrapped in a SKAction is because I sometimes want to manipulate the start frame of the animation or run the animation at a different speed. But you may or may not have the need to manipulate those aspects of your animation.
I am pretty new to SpriteKit. I have a set of nodes that need to move together to a different point for each node and after that animation completed for all of them I would like to do something else.
I was making this with UIView components before. A [UIView animateWithDuration:completion:] block was providing the thing that I needed. But in SpriteKit each node has its own action and animate by itself. I could not find any block animation for sprite nodes. So I cannot control the animation completion.
Hope, I am clear. Thanks in advance.
There are a couple of SKAction class methods that may be of interest. The first being runBlock: and the second being group: (this allows actions to be run in parallel).
You can run actions on nodes with a completion handler using runAction: completion:
The following code puts each 'animation action', along with completion block in a 'block action'. The block actions are then put into a 'group action'. Finally the group action is told to run. This causes each block action to start at the same time. The completion handler of each block action calls a method named checkCompletion. This method checks for nodes (with a specific name property value) still animating by calling hasActions on the node. Because the node that called the checkCompletion method will return YES, if only one node returns YES then all animations are done.
The following demonstrates this using two nodes, however it will work for more than two.
// assuming two SKNode subclasses named 'node1' and 'node2' AND 'self' is an SKScene subclass
// assign each animating node the same name (used in checkCompletion method)
node1.name = #"animating";
node2.name = #"animating";
// in this example both nodes will animate to the origin
CGPoint origin = CGPointMake(0.0, 0.0);
// make move actions for nodes
SKAction *shortMove = [SKAction moveTo:origin duration:1.2];
SKAction *longMove = [SKAction moveTo:origin duration:4.8];
// wrap nodes in their own separate block action
SKAction *moveNode1 = [SKAction runBlock:^{
[node1 runAction:shortMove completion:^{
NSLog(#"shortMove complete");
[self checkCompletion];
}];
}];
SKAction *moveNode2 = [SKAction runBlock:^{
[node2 runAction:longMove completion:^{
NSLog(#"longMove complete");
[self checkCompletion];
}];
}];
// put block actions in a group action
SKAction *groupAction = [SKAction group:#[moveNode1, moveNode2]];
// execute group action
[self runAction:groupAction];
checkCompletion method -
- (void)checkCompletion {
__block int currentlyHasActions = 0;
// check for all actions complete on animating nodes
[self enumerateChildNodesWithName:#"animating" usingBlock:^(SKNode *node, BOOL *stop){
if ([node hasActions]) {
currentlyHasActions++;
// prevent unnecessary enumeration
if (currentlyHasActions > 1) { *stop = YES; }
}
}];
if (currentlyHasActions == 1) {
NSLog(#"Actions Finished!!!");
// execute completion code here...
}
}
Unfortunately the following will not work as a block actions duration is instantaneous. Consequently the duration of the group action in this case is also instantaneous. This is the reason for the checkCompletion method.
[self runAction:groupAction completion:^{
// completion code here... :(
}];
What's the best way to execute a series of commands in a certain order? I keep getting frustrated by the darn concurrency of execution in ObjC. This is one of these cases where I realize that I'm a designer, not a "real" coder.
I'm experimenting with SpriteKit on iOS, and I want a sequence of things to occur when an energy gauge reaches <=0.
Call a method to create an explosion at the final contact. The method takes arguments about position and size of explosion.
Call another method afterwards that calls up a new scene, a results screen.
My problem occurs when the new scene gets called before I get a chance to see the last explosion.
Here's the relevant code:
- (void) doGameOver
{
damageIndicator.progress = 0;
energyLeft.text = #"Energy:0%";
GameOver *newScene = [[GameOver alloc]initWithSize:self.size];
newScene.timeElapsed = [started timeIntervalSinceNow];
[self.view presentScene:newScene transition:[SKTransition fadeWithColor:[SKColor whiteColor] duration:1]];
[damageIndicator removeFromSuperview];
}
- (void) makeExplosionWithSize:(float)myBoomSize inPosition:(CGPoint)boomPosition
{
NSString *myFile = [[NSBundle mainBundle] pathForResource:#"explosion" ofType:#"sks"];
SKEmitterNode *boom = [NSKeyedUnarchiver unarchiveObjectWithFile:myFile];
boom.position = boomPosition;
boom.particleSize = CGSizeMake(myBoomSize, myBoomSize);
[self addChild:boom];
[self runAction:self.playMySound];
}
- (void)adjustScoreWithDamage:(float)hitDamage atPosition:(CGPoint)pos
{
_damage = _damage -(hitDamage);
if (_damage < 0) {
//these are the two things I need to execute sequentially
[self makeExplosionWithSize:500 inPosition:pos];
[self doGameOver]
}
}
I've tried schemes using bools (gameOver = YES), but think I may need to create a completion handler, which just makes my head spin.
Can anyone suggest the easiest way to accomplish this?
Thank you in advance.
Easiest (not best) probably would be to replace
[self doGameOver];
with
[self performSelector:#selector(doGameOver) withObject:nil afterDelay:2.0];
I may misunderstand what you're going for, but it sounds like what should happen is:
The explosion begins.
There's a pause of [n] seconds.
The Game Over screen is presented.
To accomplish that, you might want to just fire "doGameOver" with an NSTimer rather than worry about having it fire immediately after the explosion completes.
Here's an example with a 3 second delay:
NSTimer *gameOverTimer = [NSTimer timerWithTimeInterval:3.0 target:self selector:#selector(doGameOver:) userInfo:nil repeats:NO];
[[NSRunLoop mainRunLoop] addTimer:gameOverTimer forMode:NSDefaultRunLoopMode];
How do I check if a SKAction has finished its animation?
I need to check if my action has already finished or is still performing its action. After that I want to create a boolean to avoid multiple actions during the main action.
SKAction *lionJumpActionComplete = [lionNode actionForKey:#"lionIsJumping"];
lionJumpActionComplete = [SKAction sequence: #[lionJumpActionUp, lionJumpActionFly, lionJumpActionDown, lionJumpActionPause]];
if (lionJumpActionComplete) {
return;
}
[lionNode runAction:lionJumpActionComplete withKey:#"lionIsJumping"];
If this is the only action running on your node, you can check this using:
if (!lionNode.hasActions) { // check if no actions are running on this node
// action code here
}
Alternatively, you can set your boolean in a completion block that gets called after the action runs and completes:
[lionNode runAction:[SKAction sequence: #[lionJumpActionUp, lionJumpActionFly, lionJumpActionDown, lionJumpActionPause]] completion:^{
BOOL isActionCompleted = YES;
}];
Here's an example of me creating a walking animation on a node. Before I create it again I make sure the previous one has finished by looking for its key.
SKAction *animAction = [self actionForKey:#"WalkingZombie"];
if (animAction) {
return; // we already have a running animation
}
[self runAction:
[SKAction animateWithTextures:[self walkAnimationFrames]
timePerFrame:1.0f/15.0f
resize:YES
restore:NO]
withKey:#"WalkingZombie"];
}
You need to check to see if the node is running the action
so in this case
if (![self hasActions]) {
[self runAction:[self actionForKey:#"ZombieAction"]];
}
probably better might be
[self runAction:[SKAction repeatForever:[self actionForKey:#"zombieAction"]]];
which will keep doing the action forever.