Removing a sprite when it goes off-screen - ios

I have a sprite that goes from left to right on the screen, when it goes off screen it won't be back. Where is the best place to deallocate it? In the -update method? Or?

you can use the didSimulatePhysics method. It is called after the update method. That we you can be sure that whatever needs to be finished in the update methods gets done, and then clean your objects or cycle through them and figure out which ones need to be removed.
- (void)didSimulatePhysics {
[self removeAllObstacles];
}
- (void)removeAllObstacles {
[blocks removeAllChildren];
[gameLayer enumerateChildNodesWithName:#"baddies" usingBlock:^(SKNode *node, BOOL *stop) {
if (node.position.x < - 100)
[node removeFromParent];
}];
}

Yes, It has to be added in the update method only. Because The update: method will be called automatically by Sprite Kit for each frame.

Related

pause a specific action on sprite

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

Store in memory NSArray with pictures or SKAction

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.

How to animate multiple SKSpriteNode together?

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... :(
}];

Dynamically allocate a parameter for action when running a sequence

I stumbled upon a problem and I can't find the answer for it. I am working with the SK template from Xcode to create an iOS game. I am a beginner, so bear with me.
Basically I have this code:
SKAction *releaseBubbles = [SKAction sequence:#[
[SKAction performSelector:#selector(createBubbleNode)onTarget:self],
[SKAction waitForDuration:speed]]];
[self runAction: [SKAction repeatAction:releaseBubbles
count:300]];
which executes in
-(id)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
I change the level to my game in -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { and when I change the level it should also change that speed parameter. Of course, this doesn't work because I believe that my action is starting when the scene is initialised and I never get to switch the parameter.
What I need to do is populate the screen continuously with bubbles appearing at a certain pace (relative to the level).
I really have no clue how to fix this, because it seems to me like I need to stop and restart the action sequence somehow...
Looking forward to your valuable input.
To continuously populate the screen with bubbles you can use the update: method of your SKScene. Here is how to do it.
First, add a property that will store a date when you last added a bubble.
#property(nonatomic, strong) NSDate *lastBubbleCreationDate;
Then, change your update: method to:
-(void)update:(CFTimeInterval)currentTime
{
// Create new bubble every 5s.
if (ABS([_lastBubbleCreationDate timeIntervalSinceNow]) > 5)
{
[self createBubbleNode];
}
}
Finally, in your createBubbleNode method you have to store the time when you created last bubble:
-(void)createBubbleNode
{
// Your code here
// Set the date to now.
_lastBubbleCreationDate = [NSDate date];
}
You also need to call createBubbleNode to set the initial value of the _lastBubbleCreationDate. You can do this in didMoveToView: method. Just add this method to your scene implementation:
- (void)didMoveToView:(SKView *)view
{
// Creates first bubble and sets the initial value of the _lastBubbleCreationDate
[self createBubbleNode];
}
In next levels you can just change the 5s value to create bubbles more often which will make the game more difficult.

iOS7 Sprite Kit how to set up a series of actions that depend on animation timing?

Just to clarify: this is not a question about sequences or groups of SKActions.
I have 7-20 sprites representing monsters on a 2D game board. I would like them to act one after another. The action can be a move, or move and attack.
The issue that I'm running in is that the first few monsters seem to act normally, - one monster moves, attacks, then the next one, etc. But after a few of them (4-5) the sequence breaks and multiple monsters start to act at the same time.
Is this an issue with the recursive move/AI methods I have below or if something within Sprite Kit is interfering with the sequence?
I see that the call stack in xCode seems to grow, and I see multiple calls to processMonsterAI, but it's not a 1 call per monster situation.
In particular I'm interested if I should use NSTimers or Delayed dispatch queue blocks instead of relying on Sprite Kit's completion blocks?
Every turn, my AI has to move monsters on the board:
-(void)processMonsterAI
{
CharacterModelNode* monster = [allMonstersCopy lastObject];
[allMonstersCopy removeLastObject];
AIBase* ai = monster.character.AI;
ai.delegate = self;
ai doAIAction];
}
AI frequently makes decisions to move around the board, which are animated as move from one cell to the next, step by step until the monster is out of moves or destination is reached:
//do recursive movement animation
-(void)recursiveMoveWithCharacter:(CharacterModelNode*)characterModel path:(NSMutableArray*)path
{
//check if we ran out of moves
if(path.count == 0)
{
//notify delegate
[self moveCompleteForCharacter:characterModel];
}else
{
NSNumber* tileIndex = path[0];
CGPoint destination = [MapOfTiles positionForTileAtIndex:tileIndex.intValue];
[path removeObjectAtIndex:0];
[self runAction:[SKAction moveTo:destination duration:1]
completion:^{
characterModel.character.currentMoves = characterModel.character.currentMoves-1;
[self recursiveMoveWithCharacter:characterModel path:path];
}];
}
}
Once the animation completes, the AI is notified that the monster is in a correct position, and attack sequence can be performed:
//once animation is complete, call
-(void)moveComplete
{
if(self.bestTarget)
{
[self.actor attack:self.bestTarget];
}
//notify whoever is the delegate that we are done here and the next AI can do it's magic.
[self finishAction];
}
//AI is done, notify delegate to process next monster
-(void)finishAction
{
NSLog(#"Finished :%#",self.actor.character.name);
[self.actor debugFlashRed];
if([self.delegate respondsToSelector:#selector(didFinishAIAction:)])
{
[self.delegate didFinishAIAction:self];
}
}
Proceed to move/attack with the next monster
//either repeat the process, or terminate if all monsters acted
-(void)didFinishAIAction:(AIBase*)AI
{
if(allMonstersCopy.count==0)
{
//finished with monster turn
[self preventUserInteraction:NO];
[[GameDataManager sharedInstance] processEndTurn];
}else
{
[self processMonsterAI];
}
}
UPDATE:
Here's the call stack, It hits the assertion that finishAction gets called by some AI more than once:
Does doAIAction possibly go through multiple paths that may call finishAction more then once? That does seem to be a possibility. There isn't really any branching in the code you have shown that would lead to multiple processMonsterAI calls.

Resources