Animate SKSpriteNode in circle, pause, and reverse? - ios

I am trying to move a SKSpriteNode in a circular path, pause that animation, and then reverse it from where it left off.
Here is what I have so far.
CGPathRef circle = CGPathCreateWithEllipseInRect(CGRectMake(self.frame.size.width/2-75,self.frame.size.height/2-75,150,150), NULL);
self.circlePathActionClockwise = [SKAction followPath:circle asOffset:NO orientToPath:YES duration:5.0];
self.circlePathActionClockwise = [SKAction repeatActionForever:self.circlePathActionClockwise];
[self.ball runAction:self.circlePathActionClockwise];
self.ball.speed = 1.0;
This animated the ball in a circle. Then when I want to pause and reverse, I try:
[self.ball runAction:[self.circlePathActionClockwise reversedAction]];
self.ball.speed = 1.0;
This reverses the animation, but it does not start it from where it left off. Instead it treats it was a new animation and starts from the beginning.
Is there a way I can start an action from self.ball's current position?
Thanks a lot!

The steps to reverse the direction of a sprite that is following a circular path are
Compute the angle of the sprite at the stopping point (see point a and angle theta in the Figure 1)
Create a new circular path that starts at angle theta
Run an action in the opposite direction
Figure 1. A sprite following a counter-clockwise circular path
Here's an example of how to do that:
Step 1: compute the starting angle of the new path based on the current position of the sprite.
- (CGFloat) angleOnCircleWithPosition:(CGPoint)position andCenter:(CGPoint)center
{
CGFloat dx = position.x - center.x;
CGFloat dy = position.y - center.y;
return atan2f(dy, dx);
}
Step 2: create a circular path given a center point and radius. Create the path starting from the specified angle (in radians).
- (CGPathRef) circlePathRefWithCenter:(CGPoint)center radius:(CGFloat)radius andStartingAngle:(CGFloat)startAngle
{
CGMutablePathRef circlePath = CGPathCreateMutable();
CGPathAddRelativeArc(circlePath, NULL, center.x, center.y, radius, startAngle, M_PI*2);
CGPathCloseSubpath(circlePath);
return circlePath;
}
You will need to declare the following instance variables:
BOOL clockwise;
SKSpriteNode *sprite;
CGFloat circleRadius;
CGPoint circleCenter;
Step 3: Reverse the direction of the sprite when the user taps the screen. It assumes that you've created a sprite and added it to the scene and set the radius and center of the circle path.
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[sprite removeActionForKey:#"circle"];
CGFloat angle = [self angleOnCircleWithPosition:sprite.position andCenter:circleCenter];
CGPathRef path = [self circlePathRefWithCenter:circleCenter radius:circleRadius andStartingAngle:angle];
SKAction *followPath = [SKAction followPath:path asOffset:NO orientToPath:YES duration:4];
if (clockwise) {
[sprite runAction:[SKAction repeatActionForever:followPath] withKey:#"circle"];
}
else {
[sprite runAction:[[SKAction repeatActionForever:followPath] reversedAction] withKey:#"circle"];
}
// Toggle the direction
clockwise = !clockwise;
}

Related

How do I create and move a node following a given path?

I have to create a simulation in landscape which displays a sine function and a node which moves on the given path (the sine wave). So far I've created the sine wave with a simple sine wave with it's period (2*PI).
The question is how do I create the simulation of an infinite wave and the node moving up and down ?
My code so far is:
(CGMutablePathRef)sineWithAmplitude:(CGFloat)amp frequency:(CGFloat)freq
width:(CGFloat)width centered:(BOOL)centered
andNumPoints:(NSInteger)numPoints {
CGFloat offsetX = 0;
CGFloat offsetY = amp;
// Center the sinusoid within the shape node
if (centered) {
offsetX = -width/2.0;
offsetY = 0;
}
CGMutablePathRef path = CGPathCreateMutable();
// Move to the starting point
CGPathMoveToPoint(path, nil, offsetX, offsetY);
CGFloat xIncr = width / (numPoints-1);
// Construct the sinusoid
for (int i=1;i<numPoints;i++) {
CGFloat y = (amp * sin(2*M_PI*freq*i/(numPoints-1))*multiplier);
CGPathAddLineToPoint(path, nil, i*xIncr+offsetX, y+offsetY);
}
return path;
And the update method:
/* Called before each frame is rendered */
self.scaleMode = SKSceneScaleModeResizeFill;
// Create an SKShapeNode
SKShapeNode *node = [SKShapeNode node];
node.position = CGPointMake(x, y);
// Assign to the path attribute
node.path = [self sineWithAmplitude:20.0 frequency:frequency1 width:400.0 centered:YES andNumPoints:70];
//node.frame.size.height
node.strokeColor = [SKColor blackColor];
[self addChild:node];
x = x+400;
Until now I only display a wave, but the result should move right and look like the image.
The circle is the node that goes up and down while the wave simulates too. Any help will be greatly appreciated.

Rotating an arrow towards a fixed point (clock like animation)

I want to rotate this arrow across a fixed point in clock. The code below doesn't do what i intend to do, it rotates the whole arrow towards a center of a circle. I also tried to animate it across a circular path but it didn't give me the effect i want ...
i want this arrow to rotate around a circle while the arrow head is pointed towards the center of this circle
-(void) addArrow2:(CGSize)size{
_Arrow2 = [SKSpriteNode spriteNodeWithImageNamed:#"arrow2"];
_Arrow2.position = CGPointMake(self.frame.size.width/2 , self.frame.size.height/2 +30);
SKAction *rotate = [SKAction rotateByAngle:M_2_PI duration:1];
SKAction *forever = [SKAction repeatActionForever:rotate];
_Arrow2.yScale = 0.45;
_Arrow2.xScale = 0.45;
[self addChild:_Arrow2];
[_Arrow2 runAction:forever];
}
I suppose your got this kind of arrow →. Change the anchorPoint as (1.0, 0.5) and make the arrow follow a circle path. Make some change to fit your need.
- (void)addArrow2
{
_Arrow2 = [SKSpriteNode spriteNodeWithImageNamed:#"arrow2"];
_Arrow2.position = CGPointMake(self.frame.size.width/2, self.frame.size.height/2+30);
_Arrow2.anchorPoint = CGPointMake(1.0, 0.5);
_Arrow2.yScale = 0.45;
_Arrow2.xScale = 0.45;
// Make a circle path
CGMutablePathRef circle = CGPathCreateMutable();
CGFloat centerX = self.frame.size.width/2;
CGFloat centerY = self.frame.size.height/2;
CGFloat radius = 25.0;
CGFloat startAngle = 0.0;
CGFloat endAngle = 2*M_PI;
CGPathAddArc(circle, NULL, centerX, centerY, radius, startAngle, endAngle, true);
CGPathCloseSubpath(circle);
SKAction *followPath = [SKAction followPath:circle asOffset:NO orientToPath:YES duration:2.0];
// Infinite rotation
SKAction *forever = [SKAction repeatActionForever:followPath];
[_Arrow2 runAction:forever];
[self addChild:_Arrow2];
}
Animation preview:

The Child SKSpriteKITnodes in the coordinate system doesn't work

The Child SKSpriteKITnodes in the coordinate system doesn't work well I am trying to move the SKSprite nodes from Top To Bottom But they Get stop in the middle ?
ColorCoin *coin = [[ColorCoin alloc] initWithImageNamed: coinName] ;
SKAction *move = [SKAction moveToY:0 duration: 10];
CGFloat screenWidth = [self dropNode].size.width;
int x = arc4random() % (int) _dropNode.calculateAccumulatedFrame.size.width;
coin.zPosition =0.0;
coin.position = CGPointMake( x, _dropNode.calculateAccumulatedFrame.size.height-30);
coin.name = coinName;
[coin runAction: move];
[[self dropNode] addChild:coin];
The anchorPoint of an SKSpriteNode is (0.5, 0.5) by default. Therefore, when [self dropNode].position.y == 0 it's correct the the child node will be in the middle of its parent SKSpriteNode.
If you want the coin to move to the bottom of dropNode you can either set the anchorPoint of dropNode to (x, 0) (where x is any value in the range 0-1). Or you can change move to:
SKAction *move = [SKAction moveToY:-screenWidth / 2 duration: 10];

How to move a sprite along a CGPath with variable velocity

I am programming a game and want to move a sprite along a fixed path (straights and curves - think railroad engine) but I want to be able to drive the animation in terms of the sprites velocity - and change that velocity in response to game events.
followPath:duration: in SKAction is close, but there seems no way to adjust the speed "on the fly" - I definitely care about sprite speed - not 'duration to follow path'.
CGPath seems like the right construct for defining the path for the sprite, but there doesn't seem to be enough functionality there to grab points along the path and do my own math.
Can anyone suggest a suitable approach?
You can adjust the execution speed of any SKAction with its speed property. For example, if you set action.speed = 2, the action will run twice as fast.
SKAction *moveAlongPath = [SKAction followPath:path asOffset:NO orientToPath:YES duration:60];
[_character runAction:moveAlongPath withKey:#"moveAlongPath"];
You can then adjust the character's speed by
[self changeActionSpeedTo:2 onNode:_character];
Method to change the speed of an SKAction...
- (void) changeActionSpeedTo:(CGFloat)speed onNode:(SKSpriteNode *)node
{
SKAction *action = [node actionForKey:#"moveAlongPath"];
if (action) {
action.speed = speed;
}
}
Try some think like this :
CGMutablePathRef cgpath = CGPathCreateMutable();
//random values
float xStart = [self getRandomNumberBetween:0+enemy.size.width to:screenRect.size.width-enemy.size.width ];
float xEnd = [self getRandomNumberBetween:0+enemy.size.width to:screenRect.size.width-enemy.size.width ];
//ControlPoint1
float cp1X = [self getRandomNumberBetween:0+enemy.size.width to:screenRect.size.width-enemy.size.width ];
float cp1Y = [self getRandomNumberBetween:0+enemy.size.width to:screenRect.size.width-enemy.size.height ];
//ControlPoint2
float cp2X = [self getRandomNumberBetween:0+enemy.size.width to:screenRect.size.width-enemy.size.width ];
float cp2Y = [self getRandomNumberBetween:0 to:cp1Y];
CGPoint s = CGPointMake(xStart, 1024.0);
CGPoint e = CGPointMake(xEnd, -100.0);
CGPoint cp1 = CGPointMake(cp1X, cp1Y);
CGPoint cp2 = CGPointMake(cp2X, cp2Y);
CGPathMoveToPoint(cgpath,NULL, s.x, s.y);
CGPathAddCurveToPoint(cgpath, NULL, cp1.x, cp1.y, cp2.x, cp2.y, e.x, e.y);
SKAction *planeDestroy = [SKAction followPath:cgpath asOffset:NO orientToPath:YES duration:5];
[self addChild:enemy];
SKAction *remove2 = [SKAction removeFromParent];
[enemy runAction:[SKAction sequence:#[planeDestroy,remove2]]];
CGPathRelease(cgpath);

how to make enemy sprite shoot at player

I'm using a sprite kit to create a game.
I have a player node which I control by touch but then I have an enemy node that moves on its own. I need the enemy to be able to shoot projectiles on its own. I'm guessing I would need a method for this and to call it. I need the direction of the projectiles to shoot wherever my player is. Any suggestions?
Here is the method I have:
-(void)monstershoot
{
SKSpriteNode * projectile = [SKSpriteNode spriteNodeWithImageNamed:#"projectile"];
projectile.position = _enemy.position;
CGPoint offset = rwSub(_player.position, projectile.position);
if (offset.x <= 0) return;
[_background addChild:projectile];
CGPoint direction = rwNormalize(offset);
CGPoint shootAmount = rwMult(direction, 1000);
CGPoint realDest = rwAdd(shootAmount, projectile.position);
// 9 - Create the actions
float velocity = 480.0/1.0;
float realMoveDuration = self.size.width / velocity;
SKAction * actionMove = [SKAction moveTo:realDest duration:realMoveDuration];
SKAction * actionMoveDone = [SKAction removeFromParent];
[projectile runAction:[SKAction sequence:#[actionMove, actionMoveDone]]];
}
But nothing happens when I call it.
It will do "nothing" in two cases: the image doesn't exist or the enemy is right of the player. Replace this line
if (offset.x <= 0) return;
by a check if the length of the offset vector is 0. You could write a method to calculate the length by using
sqrtf(powf(offset.x,2) + powf(offset.y,2))
Further you should use the length of shootAmount to calculate
float realMoveDuration = 1000 / velocity;
which will give you the same (and correct) speed for every projectile. But otherwise I see nothing wrong with your code.

Resources