Animating a CGMutablePathRef with CAShapeLayer - objective-c

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"];

Related

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).

How do I further animate an image that's on a bezier path

Maybe someone can point me in the right direction. I tried Google but dont really know how to define my question in searchable terms.
I am animating an image along a UIBezierPath like this:
UIBezierPath *trackPath = [UIBezierPath bezierPath];
[trackPath moveToPoint:P(8, 250)];
[trackPath addCurveToPoint:P(318, 250)
controlPoint1:P(156, 86)
controlPoint2:P(166, 412)];
[trackPath addCurveToPoint:P(652, 254)
controlPoint1:P(488, 86)
controlPoint2:P(512, 412)];
[trackPath addCurveToPoint:P(992, 254)
controlPoint1:P(818, 96)
controlPoint2:P(872, 412)];
CALayer *bee = [CALayer layer];
bee.bounds = CGRectMake(0, 0, 69, 71);
bee.position = P(160, 25);
bee.contents = (id)([UIImage imageNamed:#"bee3.png"].CGImage);
[self.view.layer addSublayer:bee];
What I want to do is animate the image itself as it moving along the path. I can animate the image when its b itself with like this:
NSArray *blueArray = [NSArray array];
blueArray = [[NSArray alloc] initWithObjects:
[UIImage imageNamed:#"blue1.png"],
[UIImage imageNamed:#"blue2.png"], nil];
blueFly.animationImages = blueArray;
blueFly.animationDuration = 0.20;
blueFly.animationRepeatCount = -1;
[blueFly startAnimating];
How can I combine the two, so to speak?
Additionally I know how to draw the path itself onto the screen like this:
CAShapeLayer *beeline = [CAShapeLayer layer];
beeline.path = trackPath.CGPath;
beeline.strokeColor = [UIColor whiteColor].CGColor;
beeline.fillColor = [UIColor clearColor].CGColor;
beeline.lineWidth = 2.0;
beeline.lineDashPattern = [NSArray arrayWithObjects:[NSNumber numberWithInt:6], [NSNumber numberWithInt:2], nil];
[self.view.layer addSublayer:beeline];
But how can I draw the line only behind the image as its being animated along the path?
You can use a CAShapeLayer to display the path. A shape layer provides two properties strokeStart and strokeEnd that have values between 0 and 1 and determine the fraction of the path that is visible. By animating strokeEnd you can create the impression that the path is "drawn" on the screen. See http://oleb.net/blog/2010/12/animating-drawing-of-cgpath-with-cashapelayer for an example that animates a pen that draws a line.
In order to change the image of the bee you can simply add an animation that changes the contents of your bee layer. For example, to change the image of the pen in the CAShapeLayer project above, we can add the following code to the startAnimation method.
CAKeyframeAnimation *imageAnimation = [CAKeyframeAnimation animationWithKeyPath:#"contents"];
imageAnimation.duration = 1.0;
imageAnimation.repeatCount = HUGE_VALF;
imageAnimation.calculationMode = kCAAnimationDiscrete;
imageAnimation.values = [NSArray arrayWithObjects:
(id)[UIImage imageNamed:#"noun_project_347_1.png"].CGImage,
(id)[UIImage imageNamed:#"noun_project_347_2.png"].CGImage,
nil];
[self.penLayer addAnimation:imageAnimation forKey:#"contents"];
This simply adds a key animation to the contents property of the layer that displays the pen. Note that the image "noun_project_347_1.png" is not part of the project so you would have to add an image to see the effect. I have simply horizontally flipped the image that comes with the project. By setting calculationMode to kCAAnimationDiscrete the animation does not interpolate between two images by replaces one by the other.
As long as you can get the exact path that you want to animate the image align you can do it using Core Animation and CAKeyframeAnimation.
Refer how-to-animate-one-image-over-the-unusual-curve-path-in-my-ipad-application) link.

How to Animate a UIBezierPath

i would like to animate a UIBezierPath of a rect to a triangle one, not to animate a view on a path but animate the path itself so it morphs from one shape to another. the path is added to a CALayer
CAShapeLayer *maskLayer = [CAShapeLayer layer];
maskLayer.fillColor = [[UIColor whiteColor] CGColor];
maskLayer.path = [self getRectPath];
-(CGPathRef)getTrianglePath
{
UIBezierPath* triangle = [UIBezierPath bezierPath];
[triangle moveToPoint:CGPointZero];
[triangle addLineToPoint:CGPointMake(width(self.view),0)];
[triangle addLineToPoint:CGPointMake(0, height(self.view))];
[triangle closePath];
return [triangle CGPath];
}
-(CGPathRef)getRectPath
{
UIBezierPath*rect = [UIBezierPath bezierPathWithRect:self.view.frame];
return [rect CGPath];
}
I am not an expert on CoreAnimation but you can define a CABasicAnimation as follows
CABasicAnimation *morph = [CABasicAnimation animationWithKeyPath:#"path"];
morph.duration = 5;
morph.toValue = (id) [self getTrianglePath];
[maskLayer addAnimation:morph forKey:nil];
The first line states that you want to define an animation that changes the path property of the layer. The second line states that the animation takes five seconds and the third line gives the final state of the animation. Finally, we add the animation to the layer. The key is a unique identifier for the animation, that is, if you add two animations with the same key only the last one is considered. This key can also be used to override so called implicit animations. There are a couple of properties of a CALayer that are animated by default. More precisely, if you change one of these properties the change will be animated with a duration of 0.25.

Draw image into area between two concentric circles in Xcode / objective-c

I have an image (its actually circular) and wish to only draw the part of that image that corresponds to the area between two concentric circles, the outer one is fixed, the inner one can adjust in size.
The outer concentric circle will always correspond to the outside edge of the image - but a "hole" needs to be left in the drawn image - corresponding to the size of the smaller concentric circle.
I'm trying to do this in objective-c for iOS - to draw a variable-sized "viewfinder" control.
Any help much appreciated.
Sounds like the standard case to use CGContextEOClip. Haven't checked this code, but something like:
CGContextRef ctxt = UIGraphicsGetCurrentContext();
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddEllipseInRect(path, NULL, outerCircle);
CGPathAddEllipseInRect(path, NULL, innerCircle);
CGContextEOClip(ctxt);
//Draw your stuff
The easiest and most performant solution would probably be to use a UIImageView and apply a CAShapeLayer as the mask of its layer.
Example:
UIImage *image = ...;
CGFloat ringWidth = 20.0;
CGRect outerCircleRect = CGRectMake(0, 0, image.size.width, image.size.height);
CGRect innerCircleRect = CGRectInset(outerCircleRect, ringWidth, ringWidth);
UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:outerCircleRect];
[path appendPath:[UIBezierPath bezierPathWithOvalInRect:innerCircleRect]];
CAShapeLayer *shapeLayer = [CAShapeLayer layer];
shapeLayer.fillRule = kCAFillRuleEvenOdd;
shapeLayer.path = [path CGPath];
UIImageView *imageView = [[[UIImageView alloc] initWithImage:image] autorelease];
imageView.layer.mask = shapeLayer;
[self.view addSubview:imageView];
You'll have to include the QuartzCore framework for this to work.
When you change the radius of the inner circle, you can simply assign a new path to the shape layer. This can even be animated.