Sprite kit - how to display half of sprite/texture/image - ios

I am working on a Sprite Kit project in which I need to display in some cases only half of an existing image.
I tried making the frame of the sprite smaller, but it just stretches the image.
Is there any possibility to use a mask or something in order to display only half of the sprite/image/texture?

So, in order to show only half of the image, texture, sprite, someone would need to use a SKCropNode. The only sensible thing is to crop the sprite you need starting from half, not just cropping with a predefined size. This can be achieved by setting the mask node position.
1) create a SkSpriteNode with that texture/image:
// Obj-C
SKSpriteNode *skelet = [SKSpriteNode spriteNodeWithImageNamed:imageName];
// Swift
let skelet = SKSpriteNode(imageNamed: imageName)
2) create the SKCropNode:
// Obj-C
SKCropNode * cropNode = [SKCropNode node];
// Swift
let cropNode = SKCropNode()
3) create the mask
// Obj-C
SKSpriteNode *mask = [SKSpriteNode spriteNodeWithColor:[UIColor blackColor] size:CGSizeMake(skelet.frame.size.width/2, skelet.frame.size.height)];
// Swift
let mask = SKSpriteNode(color: .black, size: CGSize(width: skelet.frame.size.width/2, height: skelet.frame.size.height))
** set the mask position to the half of the result you need (you need half of the skelet -> set the mask position to the half "of the half of the skelet")
// Obj-C
mask.position = CGPointMake(skelet.frame.size.width/4, 0);
// Swift
mask.position = CGPoint(x: skelet.frame.size.width/4, y: 0)
The division by 4 is because you need the center of the mask to be not in the center of the skelet node, but moved to the half of a half of the skelet (reminder the mask node works with a default anchor point of 0.5 0.5 - so the zero point corresponds with the center of the skelet node).
4) add the needed elements to the crop node
// Obj-C
[cropNode addChild:skelet];
[cropNode setMaskNode:mask];
[self addChild:cropNode];
// Swift
cropNode.addChild(skelet)
cropNode.maskNode = mask
self.addChild(cropNode)

Related

CAShapeLayer and SpriteKit

I am trying to make use of path animation feature in CAShapeLayer that is not available in SpriteKit and hence having to combine objects drawn suing CAShapeLayer with SpriteKit objects within the same view.
The coordinate system seems to be inverse: CAShapeLayer seems to have +ve y-axis pointing downwards, whereas SKScene has it pointing upwards.
Below is a simple XCODE playground that tries to draw a yellow line from 0,0 to 200,100 and shadow it with a thicker red line.
import SpriteKit
import PlaygroundSupport
let bounds = CGRect(x: 0, y: 0, width: 400, height: 200)
let view = SKView(frame: bounds)
view.backgroundColor = UIColor.lightGray
PlaygroundPage.current.liveView = view
// Create SK Scene
let scene = SKScene(size: CGSize(width: 400, height: 200))
scene.scaleMode = SKSceneScaleMode.aspectFit
view.presentScene(scene);
// Define the path
let path: CGMutablePath = CGMutablePath();
path.move(to: CGPoint(x:0, y:0))
path.addLine(to: CGPoint(x:200, y:100))
// Use CAShapeLayer to draw the red line
var pathLayer: CAShapeLayer = CAShapeLayer()
pathLayer.path = path
pathLayer.strokeColor = UIColor.red.cgColor
pathLayer.fillColor = nil
pathLayer.lineWidth = 4.0
pathLayer.lineJoin = kCALineJoinBevel
pathLayer.zPosition = 1;
view.layer.addSublayer(pathLayer);
//Use SKShapeNode to draw the yellow line
let pathShape: SKShapeNode = SKShapeNode(path: path);
pathShape.strokeColor = .yellow;
pathShape.lineWidth = 1.0;
pathShape.zPosition = 10;
scene.addChild(pathShape);
I expected the yellow line to coincide with the red line. Whereas, the yellow and red lines appeared as mirror images.
Is there anyway to redefine the CAShapeLayer coordinate system to point +ve Y-axis upwards?
No, as written in the docs this is how it works, you may inverse the coordinates to match your requirements..
Sprite kit docs:
The unit coordinate system places the origin at the bottom left corner of the frame and (1,1) at the top right corner of the frame. A sprite’s anchor point defaults to (0.5,0.5), which corresponds to the center of the frame.
The rest of iOS:
iOS. The default coordinate system has its origin at the upper left of the drawing area, and positive values extend down and to the right from it. You cannot change the default orientation of a view’s coordinate system in iOS—that is, you cannot “flip” it.

Adjust CGPath size

Introduction
I have a CGPath I create from a SVG file using PocketSVG API. It all works fine.
The Problem
The problem is that the shape stretches for some reason, please take a look on this picture (the blue color is just to make is more visible to you, please ignore it, it should be ClearColor):
The Target
What do I want to achieve? I want to achieve a shape that goes all over the screen's width (I don't care about the height, it should modify itself according to the width), and sticks to the bottom of the screen, please take a look on this picture as well (please ignore the circular button):
The Code
The important part ;)
I have a subclass of UIView that draws this shape from the SVG file, it called CategoriesBarView. Then, on my MainViewController (a subclass of UIViewController) I'm creating an object of CategoriesBarView and setting it programmatically as a subview.
CategoriesBarView:
class CategoriesBarView: UIView {
override func layoutSubviews() {
let myPath = PocketSVG.pathFromSVGFileNamed("CategoriesBar").takeUnretainedValue()
var transform = CGAffineTransformMakeScale(self.frame.size.width / 750.0, self.frame.size.height / 1334.0)
let transformedPath = CGPathCreateCopyByTransformingPath(myPath, &transform)
let myShapeLayer = CAShapeLayer()
myShapeLayer.path = transformedPath
let blur = UIBlurEffect(style: .Light)
let effectView = UIVisualEffectView(effect: blur)
effectView.frame.size = self.frame.size
effectView.frame.origin = CGPointMake(0, 0)
effectView.layer.mask = myShapeLayer
self.addSubview(effectView)
}
}
MainViewController:
override func viewDidLoad() {
super.viewDidLoad()
let testHeight = UIScreen.mainScreen().bounds.size.height / 6 // 1/6 of the screen’s height, that is the height in the target picture approximately, doesn’t it?
let categoriesBarView = CategoriesBarView(frame: CGRect(x: 0, y: UIScreen.mainScreen().bounds.size.height - testHeight , width: UIScreen.mainScreen().bounds.size.width, height: testHeight))
categoriesBarView.backgroundColor = UIColor.blueColor() // AS I said, it should be ClearColor
self.view.addSubview(categoriesBarView)
}
Does anyone of you know what is the problem here and why the shape is stretching like that? I'll really appreciate if someone could help me here.
Thank you very much :)
Consider following code which draws a Square of 100x100 dimension. What i have done here is taken 100x100 as a base dimension(Because its easy to calculate respective ratio or scale dimension), as you can see i have defined scaleWidth and scaleHeight variable which represents your current scale for path. Scale is 1.0 at the moment which means it draws a square of 100x100, if you change it to 0.5 and 0.75 respectively it will draw a rectangle of 50X75 pixels. Refer Images which clearly depicts difference between scale width and height as 1.0 and 0.5 and 0.75 respectively.
CGFloat scaleWidth = 0.50f;
CGFloat scaleHeight = 0.75f;
//// Square Drawing
UIBezierPath* bezierSquarePath = [UIBezierPath bezierPath];
// ***Starting point of path ***
[bezierSquarePath moveToPoint: CGPointMake(1, 1)];
// *** move to x and y position to draw lines, calculate respective x & y position using scaleWidth & scaleHeight ***
[bezierSquarePath addLineToPoint: CGPointMake(100*scaleWidth, 1)];
[bezierSquarePath addLineToPoint: CGPointMake(100*scaleWidth, 100*scaleHeight)];
[bezierSquarePath addLineToPoint: CGPointMake(1, 100*scaleHeight)];
// *** end your path ***
[bezierSquarePath closePath];
[UIColor.blackColor setStroke];
bezierSquarePath.lineWidth = 1;
[bezierSquarePath stroke];
Image 1 : Represents 100x100 square using scaleWidth = 1.0 and scaleHeight = 1.0
Image 2 : Represents 50x75 square using scaleWidth = 0.50 and scaleHeight = 0.75
Note: In given images all the drawing is done in UIView's - (void)drawRect:(CGRect)rect method as only UIView is capable to draw. I have placed a UIView which is highlighted with GrayColor in images.
I believe it gives you a perspective about scaling a path to solve your problem as you can not use the same code but you can generate one using it.
Helpful Tool : If you are not expert in Graphics coding you can recommend to use PaintCode software which generates Objective-C code with UI. Thought there might be other softwares you can opt for.
Happy coding :)

Set permanent SKSpritenode image orientation

Hi I have a bunch of round SKSpriteNodes with a circle physical body. Now when these balls roll down a path I want some of these SKSpritenodes image to stay upright even when rolling. So think of an arrow pointing upwards. When the ball starts rolling the arrow spins in circles. But for some balls Id like the arrow to remain pointing up even when the ball rolls. Whats the best way of doing this?
Edit
So an answer was given but from testing it turns out it is not the correct one. Not allowing the ball to rotate affects the way it rolls down the path. So I guess what I want is rotation to be on but the image to always appear to the user like its not rotating. Thanks.
This looks like a job for SupermSKConstraint. Constraints are evaluated and applied after the physics simulation runs on each frame, so you can use them for tasks like making a node point a certain direction regardless of what physics does to it. For this, you'd want a zRotation constraint.
But there's a bit more to it than that. If you set a zero-rotation constraint on the ball:
// Swift
let constraint = SKConstraint.zRotation(SKRange(constantValue: 0))
ball.constraints = [constraint]
You'll find that SpriteKit resets the physics body's transform every frame due to the constraint, so it only sort-of behaves like it's rolling. Probably not what you want. (To get a better idea what's going on here, try adding a zero-rotation constraint to a rectangular physics body in a world without gravity, applying an angular impulse to it, and watching it try to spin in a view with showsPhysics turned on. You'll see the sprite and its physics body get out of sync and shake a bit -- probably due to accumulated rounding errors as the physics engine and the constraint engine fight it out.)
Instead, you can do a bit of what's in 0x141E's answer, but use constraints to make it less code (and run more efficiently):
Give the ball node a circular physics body. (And possibly no texture, if the only art you want for the ball is a non-rotating sprite.)
Add the arrow node as a child of the ball node. (It doesn't need its own physics body.)
Put a zero-rotation constraint on the arrow.
Wait, that doesn't work -- I told the arrow to not rotate, but it's still spinning?! Remember that child nodes are positioned (and rotated and scaled) relative to their parent node. So the arrow isn't spinning relative to the ball, but the ball is spinning. Don't worry, you can still solve this with a constraint:
Tell the constraint to operate relative to the node containing the ball (probably the scene).
Now the constraint will keep the arrow in place while allowing the ball to rotate however the physics simulation wants it to.
Here's some test code to illustrate:
// Step 1: A rectangular spinner so we can see the rotation
// more easily than with a ball
let spinner = SKSpriteNode(color: SKColor.redColor(), size: CGSize(width: 300, height: 20))
spinner.position.x = scene.frame.midX
spinner.position.y = scene.frame.midY
spinner.physicsBody = SKPhysicsBody(rectangleOfSize: spinner.size)
scene.addChild(spinner)
spinner.physicsBody?.applyAngularImpulse(0.1) // wheeeeee
// Step 2: Make the arrow a child of the spinner
let arrow = SKSpriteNode(color: SKColor.greenColor(), size: CGSize(width: 20, height: 50))
spinner.addChild(arrow)
// Step 3: Constrain the arrow's rotation...
let constraint = SKConstraint.zRotation(SKRange(constantValue: 0))
arrow.constraints = [constraint]
// Step 4: ...relative to the scene, instead of to its parent
constraint.referenceNode = scene
Here are two methods to create a ball with a physics body and an arrow:
Add an arrow as a child of a ball
Add both the ball and the arrow directly to the scene
Here's what will happen when you add the above to the SpriteKit simulation:
The arrow will rotate when the ball rotates
Both the arrow and the ball will move/rotate independently
If you want the arrow to rotate with the ball, choose Option 1. If you want the arrow to remain fixed, choose Option 2. If you choose Option 2, you will need to adjust the rotation of the arrow to ensure that it points upward. Here's an example of how to do that.
-(void)didMoveToView:(SKView *)view {
self.scaleMode = SKSceneScaleModeResizeFill;
/* Create an edge around the scene */
self.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:view.frame];
// Show outline of all physics bodies
self.view.showsPhysics = YES;
CGFloat radius = 16;
SKNode *balls = [SKNode node];
balls.name = #"balls";
[self addChild:balls];
// Create 5 balls with stationary arrows
for (int i = 0;i<5;i++) {
// Create a shape node with a circular physics body. If you are targeting iOS 8,
// you have other options to create circular node. You can also create an SKSpriteNode
// with a texture
SKShapeNode *ball = [SKShapeNode node];
// Create a CGPath that is centered
ball.path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(-radius,-radius,radius*2,radius*2)].CGPath;
ball.fillColor = [SKColor whiteColor];
ball.position = CGPointMake(100, 100+i*radius*2);
ball.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:radius];
[balls addChild:ball];
// Create an arrow node
CGSize size = CGSizeMake(2, radius*2);
SKSpriteNode *arrow = [SKSpriteNode spriteNodeWithColor:[SKColor blackColor] size:size];
arrow.name = #"arrow";
arrow.position = CGPointZero;
[ball addChild:arrow];
// Apply angular impulse to the ball so it spins when it hits the floor
[ball.physicsBody applyAngularImpulse:-1];
}
}
- (void) didSimulatePhysics
{
SKNode *balls = [self childNodeWithName:#"balls"];
for (SKNode *ball in balls.children) {
SKNode *arrow = [ball childNodeWithName:#"arrow"];
arrow.zRotation = -ball.zRotation;
}
}
sprite.physicsBody.allowsRotation = NO;
The allowRotation property should control exactly what you are asking.

Sprite Kit - World not in drawing sprites in correct position despite skview with right dimensions

I am trying to draw a basic ground to the game for my sprite to run on.
But it seems that the ground is too short although it is suppose to take up 1/3 of the height of the screen.
My GameScene.sks is already changed to 568x320 (landscape, iPhone 5/5S)
this is my current code
func initMainGround() {
let gSize = CGSizeMake(self.size.width/4*3*2, 120);
let ground = SKSpriteNode(color: SKColor.brownColor(), size: gSize);
ground.name = gName; //Ground
ground.position = CGPointMake(0, 0);
ground.physicsBody = SKPhysicsBody(rectangleOfSize: gSize);
ground.physicsBody.restitution = 0.0;
ground.physicsBody.friction = 0.0;
ground.physicsBody.angularDamping = 0.0;
ground.physicsBody.linearDamping = 0.0;
ground.physicsBody.allowsRotation = false;
ground.physicsBody.usesPreciseCollisionDetection = true; //accurate collision
ground.physicsBody.affectedByGravity = false;
ground.physicsBody.dynamic = false;
ground.physicsBody.categoryBitMask = gBitmask; // 0x1 << 0
ground.physicsBody.collisionBitMask = pBitmask; //0x1 << 1 playerCategoryBitmask
self.addChild(ground);
}
NSLog(String(self.size.height)) return 320.0 which is perfectly fine.
But why is it that the SKSpriteNode is draw wrongly?
Setting the height of the ground to 320 only fills up half of the screen although the height of the screen in landscape is 320.
Like Jon said, this is a placement issue not a size issue. The default anchor point of any given node is in its center, so you have two options here:
1) set ground.position to CGPointMake(CGRectGetMidX(self.frame),CGRectGetMidY(self.frame))
(or even better yet, capture that as an ivar, because you'll be referring to it a whole lot when adding things to your screen, and there's no real reason to do the calculations dozens of times)
2) change the anchor point of the ground node. This is done as a CGPoint, but is interpreted as a percentage of the size of the node in question, with the default (center) being (0.5, 0.5). ground.anchorPoint = CGPointZero (which is just a shortcut for CGPointMake(0, 0)) will set the node's anchor point to its lower-left corner, at which point setting its position to (0,0) will correctly place it starting at the lower-left corner of your scene (or its parent node, in any event).

Physicsbody doesn't adhere to node's anchor point

My scene has a bunch of rectangles with physics bodies that are the same size as the rectangle. I like to anchor all of my objects to CGPointZero, however I've noticed when I do that the physicsbody remains anchored in the middle. In other words, the position of my physics body is like 100 pixels lower and to the left of the visual representation.
Here is a simple snippet of code:
SKSpriteNode* square = [SKSpriteNode spriteNodeWithColor:[SKColor blackColor] size:CGSizeMake(width, height)];
square.anchorPoint = CGPointZero; //position based on bottom-left corner
square.position = CGPointMake(x, y);
square.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:CGSizeMake(width, height)];
Any ideas or advice to solving this problem would be appreciated. For example, if I could visualize the physics bodies, that might help, but I'm not sure how to.
UPDATE: So I've solved the problem by simply not setting the anchor point and repositioning my rectangles. So the problem still exists, but I have a work around in place and the work around is working well.
I wrote this to fix Apple's lack thereof:
use pathForRectangleOfSize:withAnchorPoint: to replace your call to bodyWithRectangleOfSize: whose brief documentation tells us the problem: "Creates a rectangular physics body centered on the owning node’s origin."
#implementation SKPhysicsBody (CWAdditions)
+ (CGPathRef)pathForRectangleOfSize:(CGSize)size withAnchorPoint:(CGPoint)anchor {
CGPathRef path = CGPathCreateWithRect( CGRectMake(-size.width * anchor.x, -size.height * anchor.y,
size.width, size.height), nil);
return path;
}
+ (SKPhysicsBody *)bodyWithRectangleOfSize:(CGSize)size withAnchorPoint:(CGPoint)anchor {
CGPathRef path = [self pathForRectangleOfSize:size withAnchorPoint:anchor];
return [self bodyWithPolygonFromPath:path];
}
#end
Edit: There is a new API in 7.1 to provide for this oversight.
+ (SKPhysicsBody *)bodyWithRectangleOfSize:(CGSize)s center:(CGPoint)center
You can use
[SKPhysicsBody bodyWithRectangleOfSize:size center: center];
CGPoint center = CGPointMake(size.width*(anchorPoint.x-0.5f), size.height*(0.5f-anchorPoint.y))
should transform the bounding box according to the anchorPoint of the parents node
I am using Swift but the SKPhysicsBody was kind of half width and height wrong. I am using anchor point(0,0). Then I used the method with rectangleOfSize, center :
var cc = SKSpriteNode(color: UIColor.greenColor(), size: CGSizeMake(32, 64))
cc.physicsBody = SKPhysicsBody(rectangleOfSize: cc.size, center: CGPointMake(32/2, 64/2))
I hope it works for you too guys...thanks !
You need the anchorPoint only when you set your spriteNode's position. I don't quite understand why would you need to move physicsBody (which is the same size of node, I presume) to a corner...
But you might find useful this class method [SKPhysicsBody bodyWithPolygonFromPath:path].
Here is a nice generator for that: http://dazchong.com/spritekit/
I have run in to this myself when using SpriteKit. Unfortunately there appears to be issues based on the timing of creating a physics body and actually adding the node to the scene. If you swap the order from your code and ensure you don't do ANY physics changes until the node is actually in the render tree, that should resolve the 'weirdness' and everything will be all gravy.
Swift 4.1
let centerPoint = CGPoint(x: house1.size.width / 2 - (house1.size.width * house1.anchorPoint.x), y: house1.size.height / 2 - (house1.size.height * house1.anchorPoint.y))
house1.physicsBody = SKPhysicsBody(rectangleOf: house1.size, center: centerPoint)
Use default anchor point and it should solve your problem. I had the same problem and I had to programmatically set anchor point to x: 0.5 and y: 0.5 for every physics body in scene. This solved my problems.

Resources