Anyway to modify the destination of CCMoveTo after action started? - ios

I want to move a node and call a block after move done. But sometimes I need to move the node to another position while that node is moving (change destination).
I can't simply stop the previous action and start new one because than the callback will not be called and lead to inconsistent state.
id move = [CCMoveTo actionWithDuration:time position:pos];
id call = [CCCallBlock actionWithBlock:^{
// do something like clean up
}];
CCSequence *action = [CCSequence actions:move, call, nil];
action.tag = kMovingActionTag;
[node runAction:action];
I can get the action by CCSequence *action = (id)[node getActionByTag:kMovingActionTag]; but than the only thing I can do is stop it and lost the CCCallBlock action.
This is improved solution:
#interface CCMoveTo (SetEndPosition)
- (void)setEndPosition:(CGPoint)position;
#end
#implementation CCMoveTo (SetEndPosition)
- (void)setEndPosition:(CGPoint)position {
CGPoint pos = [target_ position];
CGFloat dis = ccpLength(delta_);
CGFloat move = ccpDistance(endPosition_, pos);
CGFloat percent = move / dis;
endPosition_ = position;
delta_ = ccpSub( endPosition_, pos);
delta_.x /= percent;
delta_.y /= percent;
startPosition_ = ccpSub(endPosition_, delta_);
}
#end

You can add a setEndPosition method using category (warning: untested, let me know in comments if this doesn't work):
#interface CCMoveTo (SetEndPosition)
- (void)setEndPosition:(CGPoint)position;
#end
#implementation CCMoveTo (SetEndPosition)
- (void)setEndPosition:(CGPoint)position {
endPosition_ = position;
delta_ = ccpSub( endPosition_, startPosition_ );
}
#end
Then, modify the destination by calling [move setEndPosition:newPosition] (meaning you have to make the move variable accessible to the code portion which will modify the destination).

Related

How can I countdown before I resume a SKScene?

I have a paused SKScene. When the user asks to do so, I would like to resume the scene. However, I would like to give the user a couple of seconds to prepare before the game begins. To do so, when the user asks to resume the game, I would like to first countdown from 3, and then resume the scene.
I currently have a SKLabel to indicate the count. When the user clicks on resume, I use an NSTimer to countdown from 3, updating the contents of the label every second, and resuming the game when the count is up.
However, because the game is paused, the SKLabel doesn't update every second; it only updates once at the very end, once the game is resumed. I am looking for a way around this.
Use a common variable in your GameScene that indicates if the game is paused, for example isGamePaused. In your update: method, you will have:
if(!isGamePaused){
//Do all game logic
}
You can use isGamePaused to pause and unpause the game. Now, let's make the countdown. I would make a subclass of SKNode, add the SKLabel there and set a delegate so we know when the CountDown finished. For example, in CountDown.h:
#protocol SKCountDownDelegate <NSObject>
-(void)countDown:(id)countDown didFinishCounting:(BOOL) didFinishCounting;
#end
#interface SKCountDown : SKNode
#property (assign, atomic) id<SKCountDownDelegate> delegate;
-(instancetype)initCountDownWithSeconds:(int)seconds andColor:(UIColor *) color andFontName:(NSString *)fontName;
#end
And in CountDown.m:
#import "SKCountDown.h"
#interface SKCountDown()
#property int secondsToGo;
#property SKLabelNode *numberLabel;
#property NSTimer *countTimer;
#end
#implementation SKCountDown
-(instancetype)initCountDownWithSeconds:(int)seconds andColor:(UIColor *)color andFontName:(NSString *)fontName{
if (self = [super init]) {
self.numberLabel = [[SKLabelNode alloc] initWithFontNamed:fontName];
[self.numberLabel setFontColor:color];
[self.numberLabel setFontSize:110];
[self.numberLabel setText:#"3"];
self.secondsToGo = seconds;
[self addChild:self.numberLabel];
self.countTimer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(count) userInfo:nil repeats:YES];
}
return self;
}
-(void)count{
if (self.secondsToGo > 1) {
self.secondsToGo -= 1;
self.numberLabel.text = [NSString stringWithFormat:#"%i", self.secondsToGo];
}else{
[self.countTimer invalidate];
self.countTimer = nil;
self.numberLabel.text = #"GO!";
[self performSelector:#selector(finish) withObject:nil afterDelay:1];
}
}
-(void)finish{
[self removeFromParent];
[self.delegate countDown:self didFinishCounting:YES];
}
#end
So, anywhere you want to add this CountDown, you can do the following:
-(void)startCountDown{
self.countDown = [[SKCountDown alloc] initCountDownWithSeconds:3 andColor:self.countdownFontColor andFontName:self.countdownFontName];
self.countDown.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));
self.countDown.delegate = self;
self.countDown.zPosition = 20;
[self addChild:self.countDown];
}
-(void)countDown:(id)countDown didFinishCounting:(BOOL)didFinishCounting{
isGamePaused = NO;
}
This is an example of a way to do it. Hope it helps!
Pausing an entire SKScene during gameplay seems a bit too blunt for me, because you typically want to update something in the node hierarchy.
A simple solution would be to make a distinction between game node and UI nodes. You can simply add two nodes (game and ui) to the scene and whenever the scene would call [self addChild] choose which node to add them to.
Later, you can pause the game node and still be able to update the UI.
SKScene
- SKNode "game" // <-- pause this one
- SKSpriteNode "goodGuy"
- SKSpriteNode "badGuy"
- SKSpriteNode "mehGuy"
- ...
- SKNode "ui"
- SKLabelNode "countdownLabel"
- SKSpriteNode "resumeButton"
- SKSpriteNode "pauseButton"
- ...
Do take note that the paused property only determines if actions are processed on the node and its descendants. If you are performing physics this will not suffice.
Pausing physics can be done by pausing the SKView but that is definitely not what you're looking for. You can always try setting the speed of the physicsWorld to 0 (I haven't tested this, though).
I prefer to simply pause sections of the update method instead of pausing the whole scene. Create a BOOL property:
#property (nonatomic) BOOL runUpdateMethod;
Structure your update method to determine what pieces get paused:
-(void)update {
if(runUpdateMethod) {
// code to be paused
}
// code not to be paused
}
You can use a SKAction block to run a countdown and resume regular updates:
-(void)countdown {
__block SKLabelNode *labelNode0;
SKAction *wait0 = [SKAction waitForDuration:1.0];
SKAction *block0 = [SKAction runBlock:^{
labelNode0 = [SKLabelNode labelNodeWithFontNamed:#"Arial"];
labelNode0.text = #"5";
labelNode0.fontSize = 100;
labelNode0.fontColor = [SKColor redColor];
labelNode0.horizontalAlignmentMode = SKLabelHorizontalAlignmentModeCenter;
labelNode0.verticalAlignmentMode = SKLabelVerticalAlignmentModeCenter;
labelNode0.position = CGPointMake(self.screenWidth/2, self.screenHeight/2);
labelNode0.zPosition = 955;
labelNode0.alpha = 0.4;
[self addChild:labelNode0];
}];
SKAction *block1 = [SKAction runBlock:^{
labelNode0.text = #"4";
}];
SKAction *block2 = [SKAction runBlock:^{
labelNode0.text = #"3";
}];
SKAction *block3 = [SKAction runBlock:^{
labelNode0.text = #"2";
}];
SKAction *block4 = [SKAction runBlock:^{
labelNode0.text = #"1";
}];
SKAction *block5 = [SKAction runBlock:^{
[labelNode0 removeFromParent];
self.runUpdateMethod = YES;
}];
[self runAction:[SKAction sequence:#[block0, wait0, block1, wait0, block2, wait0, block3, wait0, block4, wait0, block5]]];
}
Instead of pausing the game, I set SKScene's speed property to 0, along with setting SKPhysicsWorld's speed property to 0. I also had to modify my update function slightly by only executing certain portions of the code if the game isn't paused.
This created the same effect as the entire game being paused, while also allowing me to update the content of labels.

CCNode remove not working Cocos2d

For some reason my remove function on my CCNode is not working after the animation is run.
I am adding a CCNode on click, running a animation then running the remove function.
// loads the Coin
CCNode* coin = [CCBReader load:#"heros/coin"];
coin.name =#"coin";
// position
coin.position = ccpAdd(_touchNode.position, ccp(0, 0));
//Add to Parent
[_touchNode addChild:coin z:-1];
id action1 = [CCActionMoveTo actionWithDuration:0.7f position:ccpAdd(_touchNode.position, ccp(0, 200))];
id action2 = [CCActionMoveTo actionWithDuration:0.7f position:ccpAdd(_touchNode.position, ccp(0, 100))];
CCActionCallFunc *callAfterMoving = [CCActionCallFunc actionWithTarget:self selector:#selector(cleanupSprite:)];
[coin runAction: [CCActionSequence actions:action1, action2, callAfterMoving, nil]];
using the following delete function produces a crash error with This node does not contain the specified child
- (void) cleanupSprite:(CCNode*)inSprite
{
// call your destroy particles here
// remove the sprite
[self removeChild:inSprite cleanup:YES];
}
Using the following delete also doesn't work
- (void) cleanupSprite:(CCNode*)inSprite
{
// call your destroy particles here
// remove the sprite
[self removeChildByName:#"coin" cleanup:YES];
}
self does not contain coin, _touchNode does. Do this in your callback :
[_touchNode removeChildByName:#"coin" cleanup:YES];

Change GMSCircle radius with Animation

I am using Google Maps iOS sdk for my app .In my app user can draw a fence(a circle) and later can edit to change and resize the radius of circle .
Its resizing properly but when radius value changes its instant,not a smooth animation like map zoom in/out.Is it achievable with latest GMaps sdk for ios?
Apparently, its not possible becuase what i see is GMSCircle is inherited from GMSOverlay which is child of NSObject,so its defineltly not a view,rather that overlay is drawn with some layer or something like that .
Any help is appreciated..!!
Thanks..!!
I found that you could just change the radius, and the circle changes.
So I wrote a helper class to do this:
#interface TAMapCircle : GMSCircle
{
CLLocationDistance _from;
CLLocationDistance _to;
NSTimeInterval _duration;
}
#property (nonatomic, copy) void(^handler)();
#property (nonatomic, strong) NSDate * begin;
#end
#implementation TAMapCircle
// just call this
-(void)beginRadiusAnimationFrom:(CLLocationDistance)from
to:(CLLocationDistance)to
duration:(NSTimeInterval)duration
completeHandler:(void(^)())completeHandler {
self.handler = completeHandler;
self.begin = [NSDate date];
_from = from;
_to = to;
_duration = duration;
[self performSelectorOnMainThread:#selector(updateSelf) withObject:nil waitUntilDone:NO];
}
// internal update
-(void)updateSelf {
NSTimeInterval i = [[NSDate date] timeIntervalSinceDate:_begin];
if (i >= _duration) {
self.radius = _to;
self.handler();
return;
} else {
CLLocationDistance d = (_to - _from) * i / _duration + _from;
self.radius = d;
// do it again at next run loop
[self performSelectorOnMainThread:#selector(updateSelf) withObject:nil waitUntilDone:NO];
}
}
#end
Hopefully my answer can help.
I have done something similar using CADisplayLink. It makes it very easy to do simple animations in a GMSMapView.
https://developer.apple.com/library/prerelease/ios/documentation/QuartzCore/Reference/CADisplayLink_ClassRef/index.html
You need to wrap the animation changes that you are attempting to make in UIView animation block. This has changed in the latest version of the iOS SDK such that the method signature is [UIView beginAnimation:]. Wrap any changes you make in this block and they will be animated by UIKit. If that doesn't work you can go to a lower level with CoreAnimation transactions.

Cocos2D magnification - making the code dynamic and using an image

I have found the following code and I need help with editing it. I am not really familiar with texture rendering.
First of all, init method takes a rect and magnifies only that area? How can I make it more dynamic and magnify only whatever is underneath the magnifying glass?
Secondly, Is it possible to change the shape to circle rather than rectangle?
Or Can I use an image as the frame of the magnifying glass?
Here is the code..
Cheers..
.h file
#import <Foundation/Foundation.h>
#import "cocos2d.h"
#interface Magnify : CCNode {
BOOL active;
CGRect rect;
CGFloat magnifyScale;
CCNode *renderNode;
CCRenderTexture *renderTexture;
}
- (id)initWithNodeToMagnify:(CCNode *)n rect:(CGRect)rectToMagnify scale:(CGFloat)scale;
- (void)enable;
- (void)disable;
.m file
#import "Magnify.h"
#implementation Magnify
- (id)initWithNodeToMagnify:(CCNode *)n rect:(CGRect)rectToMagnify scale:(CGFloat)scale
{
if (self = [super init]) {
self.visible = active = NO;
renderNode = n;
rect = rectToMagnify;
magnifyScale = scale;
renderTexture = [[CCRenderTexture renderTextureWithWidth:rect.size.width height:rect.size.height] retain];
[self addChild:renderTexture];
}
return self;
}
- (void)enable
{
self.visible = active = YES;
[self scheduleUpdate];
}
- (void)disable
{
self.visible = active = NO;
[self unscheduleUpdate];
}
- (void)drawAreaToTexture
{
[renderTexture beginWithClear:0.0 g:0.0 b:0.0 a:1.0];
// shift the renderNode's position to capture exactly the rect we need
CGPoint originalPosition = renderNode.position;
renderNode.position = ccpSub(originalPosition, rect.origin);
// scale the node as we want
CGFloat originalScale = renderNode.scale;
renderNode.scale = magnifyScale;
[renderNode visit];
// shift renderNode's position back
renderNode.position = originalPosition;
// scale back
renderNode.scale = originalScale;
[renderTexture end];
}
- (void)update:(ccTime)dt
{
[self drawAreaToTexture];
}
- (void)dealloc
{
[renderTexture release];
[super dealloc];
}
#end
OK, so, as I mentioned above for something like this, one possible answer is to use the CCLens3D class to get the "effect" of magnifying something in a circular manner.
I found using this to be a little tricky because it doesn't seem to work unless it's a child of the top level node of your 'scene'.
Here is some code I use to create a lens that moves around the screen, and then disappears:
// Create the lens object first.
//
CCLens3D *lens =
[CCLens3D actionWithPosition:fromPos
radius:50
grid:ccg(50, 50)
duration:2.0];
// Set the "size" of the lens effect to suit your needs.
//
[lens setLensEffect:1.0];
// In my case, I then move the lens to a new position. To apply an action on
// a lens, you need to give the actions to the actionManager in the
// CCDirector instance.
//
CCMoveTo *move = [CCMoveTo actionWithDuration:2.0 position:toPos];
// I had another action in this array, but this will do.
//
CCSequence *seq = [CCSequence actions:move, nil];
// Now tell the actionManager to move the lens. This is odd, but it works.
//
[[[CCDirector sharedDirector] actionManager] addAction:seq target:lens paused:NO];
// Now just for some more weirdness, to actually make the lens appear and operate
// you run it as an action on the node it would normally be a child of. In my case
// 'self' is the CCLayer object that is the root of the current scene.
//
// Note that the first action is the lens itself, and the second is a special
// one that stops the lens (which is a "grid" object).
//
[self runAction:[CCSequence actions:lens, [CCStopGrid action], nil]];
I imagine that you should be able to stop the grid by running the CCStopGrid action when you want to. In my case it is a programmed thing. In yours it might be when the user lets go of a button.

Cocos2d game crashing shortly after CCCallFunc

Situation:
I'm getting some mysterious crashing shortly after a CCCallFunc. In short, we have a button. The button has a tag to identify it later. When the button is pressed, we run some actions to animate it, and when the animation is done, we CCCallFunc another method to transition to another scene. We crash shortly after the CCCallFunc. Source and errors below.
Point Of Crash (in cocos2d source):
// From CCActionInstant.m of cocos2d
-(void) execute
{
/*** EXC_BAD_ACCESS on line 287 of CCActionInstant.m ***/
[targetCallback_ performSelector:selector_];
}
#end
Snapshot of Thread 1:
My Code:
Below is some source taken from MenuLayer.m (a simple menu to display a button).
// from MenuLayer.m
// …
#implementation MenuLayer
-(id) init{
if((self=[super init])) {
/****** Create The Play Button (As a CCMenu) ********/
CCSprite *playSprite = [CCSprite spriteWithFile:#"playbutton.png"];
CCMenuItemSprite *playItem = [CCMenuItemSprite itemFromNormalSprite:playSprite selectedSprite:nil target:self selector:#selector(animateButton:)];
playItem.tag = 3001;
playItem.position = ccp(160.0f, 240.0f);
CCMenu *menu = [CCMenu menuWithItems:playItem, nil];
menu.position = ccp(0.0f, 0.0f);
[self addChild:menu z:0];
}
}
// ...
- (void)animateButton:(id)sender{
/*** Run an animation on the button and then call a function ***/
id a1 = [CCScaleTo actionWithDuration:0.05 scale:1.25];
id a2 = [CCScaleTo actionWithDuration:0.05 scale:1.0];
id aDone = [CCCallFunc actionWithTarget:self selector:#selector(animationDone:)];
[sender runAction:[CCSequence actions:a1,a2,aDone, nil]];
}
- (void)animationDone:(id)sender{
/*** Identify button by tag ***/
/*** Call appropriate method based on tag ***/
if([(CCNode*)sender tag] == 3001){
/*** crashes around here (see CCActionInstant.m) ***/
[self goGame:sender];
}
}
-(void)goGame:(id)sender{
/*** Switch to another scene ***/
CCScene *newScene = [CCScene node];
[newScene addChild:[StageSelectLayer node]];
if ([[CCDirector sharedDirector] runningScene]) {
[[CCDirector sharedDirector] replaceScene:newScene]];
}else {
[[CCDirector sharedDirector] runWithScene:newScene];
}
}
Use CCCallFuncN instead of CCCallFun.
CCCallFuncN passes the Node as parameter, the problem with CCCallFun is that you are loosing reference of the node.
I test your code with CCCallFuncN and works ok.
Just a hunch. Besides checking for memory leaks, try to schedule a selector with a 0 second interval instead of directly sending the goGame message. I have a suspicion that director's replaceScene causes a cleanup of the scene and all objects associated with it. That in turn could leave the CCCallFunc action in an undefined state. Although normally it works fine - which is to say that this is just another indication about something sketchy, memory- respectively object-lifetime-management-wise.
Btw, if you support iOS 4 as a minimum, use CCCallBlock instead of CCCallFunc. That's safer and cleaner.

Resources