Using SKAction follow path on a node on an SKNode that moves doesn't work - ios7

Sorry if that doesn't make sense, here's an example chunk of code:
Scene.m
self.anchorPoint = CGPointMake(0.5, 0);
background = [SKSpriteNode spriteNodeWithImageNamed:#"background.png"];
center = [SKSpriteNode spriteNodeWithImageNamed:#"center.png"];
[self addChild:background];
[background addChild:center];
float xpos = 0;
float ypos = 20*20;
SKShapeNode *block = [SKShapeNode shapeNodeWithRect:CGRectMake(xpos, ypos, 20, 20)];
block.fillColor = [SKColor blueColor];
block.name = #"block";
[background addChild:block];
CGMutablePathRef path = CGPathCreateMutable();
CGPathMoveToPoint(path, NULL, xpos, ypos);
CGPathAddCurveToPoint(path, NULL, 0, 0, xpos + 100, ypos / 2, 0, 0);
SKAction *followline = [SKAction followPath:path asOffset:YES orientToPath:YES duration:10];
[block runAction:followline];
What happens is the path is offscreen on the bottom half where the rest of the background image is. Is this an anchor issue, a position issue? If I rotate the background node things get all messed up to but even in this simple case the path is not working as I would expect it to.

You can add a node that follows a path as a child of another node that moves. The reason the node in your app doesn't follow the path as expect is it's an SKShapeNode, which doesn't work well with followPath SKActions. If you change the shape node to a SKSpriteNode, the node will follow the path as expected.

Related

Animating a CGMutablePathRef with CAShapeLayer

I'm trying to animate a path and with the changing of it's points, as shown in the keyframes below. So, the top two points move down to the bottom and the middle point moves to the top, essentially flipping it's direction.
Here's the code I'm using to create the path:
CGMutablePathRef path = CGPathCreateMutable();
CGPathMoveToPoint(path, nil, 1.0f, 8.0f);
CGPathAddLineToPoint(path, nil, 7.0f, 1.5f);
CGPathAddLineToPoint(path, nil, 13.0f, 8.0f);
shapeLayer = [[CAShapeLayer alloc] init];
shapeLayer.path = path;
shapeLayer.strokeColor = pathColor.CGColor;
shapeLayer.fillColor = [NSColor clearColor].CGColor;
shapeLayer.lineWidth = 2.0f;
[self.layer addSublayer: shapeLayer];
However, I've also got little idea of how to actually animate the points. All the information about animating paths online is simply changing the path start, not the actual point.
Is there a way - or a different approach - to do this? Thanks in advance.
You can animate the path of the shape layer from one path to another.
In your case you would create the paths for the initial state and for the end state as two different paths.
Note: if your two paths have a different number of segments or control points, the animation will look very strange.
Then you would create an animation for the "path" key path with those to and from values and add it to the layer you are animating. If you also want the property to change to the end state you should also update the path property to reflect the end state of the animation.
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"path"];
animation.duration = 1.0; // however long your duration should be
animation.fromValue = (__bridge id)fromPath;
animation.toValue = (__bridge id)toPath;
shapeLayer.path = toPath; // the property should reflect the end state of the animation
[shapeLayer addAnimation:animation forKey:#"myPathAnimation"];

SKShapeNode.Get Radius?

Is it possible to get an SKShapeNode's radius value?
The problem is, I need to create an identical circle after the user releases it, whilst removing the first circle from the view.
SKShapeNode *reticle = [SKShapeNode shapeNodeWithCircleOfRadius:60];
reticle.fillColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:0.3];
reticle.strokeColor = [UIColor clearColor];
reticle.position = CGPointMake(location.x - 50, location.y + 50);
reticle.name = #"reticle";
reticle.userInteractionEnabled = YES;
[self addChild:reticle];
[reticle runAction:[SKAction scaleTo:0.7 duration:3] completion:^{
[reticle runAction:[SKAction scaleTo:0.1 duration:1]];
}];
Then
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
....
SKNode *node = [self childNodeWithName:#"reticle"];
//take the position of the node, draw an imprint
/////////Here is where I need to get the circle radius.
[node removeFromParent];
}
How does one take the circle radius, so I can then say
SKShapeNode *imprint = [SKShapeNode shapeNodeWithCircleOfRadius:unknownValue];
I've tried, making an exact copy of the circle with
SKShapeNode *imprint = (SKShapeNode *)node;
However, this still follows the animation where as I need it to stop, at the point it was at. I don't want to take a "stopAllAnimations" approach.
You can use the shape node's path property to determine the bounding box of the shape with CGPathGetBoundingBox(path). From the bounds, you can compute the radius of the circle by dividing the width (or height) by 2. For example,
CGRect boundingBox = CGPathGetBoundingBox(circle.path);
CGFloat radius = boundingBox.size.width / 2.0;
Updated for Swift 5
myVariable.path.boundingBox.width / 2

SKShapeNode not maintaining center position while applying scale

I'm starting to develop a game, and in a moment I need to show a circle which increases its size, I got that, but the problem is that it increases its size and change its position and finally disappear from the screen. I want it to stay on its initial position and increase its size from the initial center.
My code is the following:
CGRect circle = CGRectMake(skRand(0, self.size.width), skRand(0, self.size.height), 20, 20);
SKShapeNode *shapeNode = [[SKShapeNode alloc] init];
shapeNode.position = CGPointMake(skRand(0, self.size.width), skRand(0, self.size.height));
shapeNode.path = [UIBezierPath bezierPathWithOvalInRect:circle].CGPath;
shapeNode.fillColor = [SKColor blueColor];
shapeNode.strokeColor = nil;
[self addChild:shapeNode];
SKAction* zoom = [SKAction scaleTo:15.0 duration:10.0];
[shapeNode runAction:zoom];
Any ideas?
Than you very much!
If you want your circle to increase size from the center, try constructing the circle not by using CGRectMakebut instead use a CGMutablePathRef, make the path and set the circle's path to the one you created. I have something similar in my game and this worked for me:
SKShapeNode *turnSphere = [[SKShapeNode alloc] init];
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddArc(path, NULL, 0, 0, 30, 0.0, (2 * M_PI), NO);
turnSphere.path = path;
turnSphere.position = CGPointMake(220, 440);
[self addChild:turnSphere];
[turnSphere runAction:[SKAction scaleBy:3 duration:2]];
The circle should now scale up from the center. Hope this works!

Incorrect work of SKPhysicsBody with moved SKSpriteNode anchorPoint?

Here is my code:
// setting background
_ground = [[SKSpriteNode alloc]
initWithColor:[SKColor greenColor]
size:CGSizeMake([Helpers landscapeScreenSize].width, 50)];
_ground.name = #"ground";
_ground.zPosition = 1;
_ground.anchorPoint = CGPointMake(0, 0);
_ground.position = CGPointMake(0, 0);
_ground.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:CGSizeMake([Helpers landscapeScreenSize].width, 50)];
_ground.physicsBody.dynamic = NO;
_ground.physicsBody.affectedByGravity = NO;
_ground.physicsBody.categoryBitMask = groundCategory;
_ground.physicsBody.collisionBitMask = elementCategory;
_ground.physicsBody.contactTestBitMask = elementCategory;
Green rectangle positioning is ok, but SKPhysicsBody is not representing this rectangle correctly. It looks like SKPhysicsBody is not "moving" its body according to sprite position and anchorPoint. SKPhysicsBody is moved left for _ground.width points.
What am I doing wrong?
PS.
Changing to this (code) solved my problem, but I really dont like this solution, it seems an ugly way to position an element.
_ground.position = CGPointMake([Helpers landscapeScreenSize].width / 2, 0);
+removing:
_ground.position = CGPointMake(0, 0);
I had exactly the same issue yesterday ;)
For me I solved this by using the default sprite positioning by SpriteKit. I just set the position and leave the anchor point at it's default. Then you set the physicsbody to the size of the sprite.
Change your code to the following and check if it works:
_ground = [[SKSpriteNode alloc]
initWithColor:[SKColor greenColor]
size:CGSizeMake([Helpers landscapeScreenSize].width, 50)];
_ground.position = CGPointMake([Helpers landscapeScreenSize].width / 2, 0);
_ground.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:_ground.size];
If you still have no luck with this you can also try to use another initialisation for your sprite (this works in my project). Try it with a texture:
[[SKSpriteNode alloc] initWithTexture:[SKTexture textureWithCGImage:[UIImage imageWithColor:[UIColor whiteColor]].CGImage] color:[UIColor whiteColor] size:CGSizeMake(PLAYER_WIDTH, PLAYER_HEIGHT)];
don't forget to implement the (UIImage*)imageWithColor: method to create a 1x1 pixel image of your desired color for you (see here).

Circular-slider weird behaviour while animating with CA

I wanted to make a circular Slider which is draggable and animatable. So far I've managed to build the slider and use the drag handle to move it around and even animate it. Sometimes animation goes wrong (wrong direction or shortest direction. I've subclassed a UIView (Will be a UIControl soon, just wanted to get the animation right first) added a PanGestureRecognizer and several layers for the drawing.
So how do I fix this weird behaviour? I've someone could help me here, I'd be thankful :)
Here's the sample project -> http://cl.ly/2l0O3b1I3U0X
Thanks a lot!
EDIT:
Here's the drawing code:
CALayer *aLayer = [CALayer layer];
aLayer.bounds = CGRectMake(0, 0, 170, 170);
aLayer.position = self.center;
aLayer.transform = CATransform3DMakeRotation(M_PI_4, 0, 0, 1);
self.handleHostLayer = [CALayer layer];
self.handleHostLayer.bounds = CGRectMake(0, 0, 170, 170);
self.handleHostLayer.position = CGPointMake(CGRectGetMaxX(aLayer.bounds) - 170/2.0, CGRectGetMaxY(aLayer.bounds) - 170/2.0);
[aLayer addSublayer:self.handleHostLayer];
[self.layer addSublayer:aLayer];
self.handle = [CALayer layer];
self.handle.bounds = CGRectMake(0, 0, 50, 50);
self.handle.cornerRadius = 25;
self.handle.backgroundColor = [UIColor whiteColor].CGColor;
self.handle.masksToBounds = NO;
self.handle.shadowOffset = CGSizeMake(3.0, 0.0);
self.handle.shadowRadius = 0;
self.handle.shadowOpacity = .15;
self.handle.shadowColor = [UIColor blackColor].CGColor;
[self.handleHostLayer addSublayer:self.self.handle];
Here's the animation code:
CGFloat handleTarget = ToRad(DEG);
CABasicAnimation *rotationAnimation = [CABasicAnimation animationWithKeyPath:#"transform.rotation"];
rotationAnimation.fromValue = #([[self.handleHostLayer valueForKeyPath:#"transform.rotation"] floatValue]);
rotationAnimation.toValue = #(handleTarget);
rotationAnimation.duration = .5;
rotationAnimation.removedOnCompletion = NO;
rotationAnimation.fillMode = kCAFillModeForwards;
rotationAnimation.cumulative = YES;
rotationAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
[self.handleHostLayer addAnimation:rotationAnimation forKey:#"transform.rotation"];
OK, I looked at your project. Your problem is that the to and from angles don't both fall in the 0≤ɸ<2π range.
You can make sure that they do by adding and removing 2π until they both are within that range.
CGFloat fromAngle = [[self.handleHostLayer valueForKeyPath:#"transform.rotation"] floatValue];
CGFloat toAngle = handleTarget;
while (fromAngle >= 2.0*M_PI) { fromAngle -= 2*M_PI; }
while (toAngle >= 2.0*M_PI) { toAngle -= 2*M_PI; }
while (fromAngle < 0.0) { fromAngle += 2*M_PI; }
while (toAngle < 0.0) { toAngle += 2*M_PI; }
CABasicAnimation *rotationAnimation = [CABasicAnimation animationWithKeyPath:#"transform.rotation"];
rotationAnimation.fromValue = #(fromAngle);
rotationAnimation.toValue = #(toAngle);
// As before...
Another things you could change is the misuse of removeOnCompletion. I did a long explanation in this answer about why it's bad to do so.
In short: you are not animating from the value that you think you are animating since you inadvertently introduce a difference between the value of the layers property and what you see on screen.
Skip that line all together. You can also skip the skip the cumulative line since you are not repeating the animation. Now, if your animations doesn't stick: set the model value to its final value before adding the animation to it.