SKShapeNode not maintaining center position while applying scale - objective-c

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!

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

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

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.

Physics Bodies are being offset from their node upon creation

I have been working on a puzzle game that uses a customizable grid with levels that are preset and created when the scene inits. They are comprised of static blocks scattered around the grid and a movable player controlled block. The gravity of the world is controllable and the block falls in that direction (up, down, left, right). I am using SKContactDelegate to track when the player block touches a block or the edge of the grid and stops it in place.
The problems I am having involve the physics bodies of the blocks and grid edge.
I am using bodyWithRectOfSize for the blocks and bodyWithEdgeLoopFromRect for the grid border.
The physics bodies of 1x1 blocks placed in the grid and the player 1x1 block have normal physics bodies (as they should be). However, larger blocks ex: 1x5, the bodies are shifted down on the y axis for no reason. Also depending on the grid size, the grid edge would be offset by a random number. Note: the bodies are offset and the nodes themselves are in the right place.
This is the code for creating the blocks; this one works fine
(cellsize is the size of each grid space)
Basic 1x1 block
-(SKShapeNode *)basic {
CGRect blockRect = CGRectMake(10, 10, self.cellSize, self.cellSize);
UIBezierPath *blockPath = [UIBezierPath bezierPathWithRoundedRect:blockRect cornerRadius:8];
SKShapeNode *blockNode = [SKShapeNode node];
blockNode.path = blockPath.CGPath;
blockNode.fillColor =[UIColor blackColor];
blockNode.lineWidth = 0;
blockNode.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:blockRect.size];
blockNode.physicsBody.categoryBitMask = blockCategory;
blockNode.physicsBody.dynamic = NO;
return blockNode;
}
and custom blocks (offset down on the y axis)
-(SKShapeNode *)basicWithWidth:(int)width WithHeight:(int)height {
CGRect blockRect = CGRectMake(10, 10, self.cellSize * width, self.cellSize * height);
UIBezierPath *blockPath = [UIBezierPath bezierPathWithRoundedRect:blockRect cornerRadius:8];
SKShapeNode *blockNode = [self basic];
blockNode.path = blockPath.CGPath;
blockNode.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:blockRect.size];
blockNode.physicsBody.categoryBitMask = blockCategory;
blockNode.physicsBody.dynamic = NO;
return blockNode;
}
And here is the grid edge
SKShapeNode *edge = [SKShapeNode node];
CGRect edgeRect = CGRectMake(10, 10, 260, 260);
UIBezierPath *edgeShape = [UIBezierPath bezierPathWithRoundedRect:edgeRect cornerRadius:8];
edge.path = edgeShape.CGPath;
edge.lineWidth = 0;
edge.fillColor = [UIColor grayColor];
edge.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:edgeRect];
edge.physicsBody.categoryBitMask = boardCategory;
[self addChild:edge];
Note: The edge and blocks are children to a "board" node that is a child of the game scene
Thank you for reading this.
When you use SKPhysicsBody's bodyWithRectangleOfSize:, it is centered on its node's origin. What you want to do in your case is center them on the center of the rect that defines your node. To do that, use bodyWithRectangleOfSize:center: instead.
For example, you define your block with the rect:
CGRect blockRect = CGRectMake(10, 10, self.cellSize, self.cellSize);
So, you'll want your physicsBody centered on the center of that rect. You easily get the center of a CGRect with:
CGPoint center = CGPointMake(CGRectGetMidX(blockRect), CGRectGetMidY(blockRect));
To create the physicsBody centered there, use:
blockNode.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:blockRect.size center:center];
Note: bodyWithRectangleOfSize:center: is only available in iOS 7.1 and later
iOS 7.0 method
For iOS 7.0, you can use bodyWithPolygonFromPath: instead, it just takes an additional line of code:
CGPathRef blockPath = CGPathCreateWithRect(blockRect, nil);
blockNode.physicsBody = [SKPhysicsBody bodyWithPolygonFromPath: blockPath];

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

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.