I'm new to SpriteKit and I'm trying to create a game, where blocks would fall from top of the screen and land on the bottom of the screen or on top of another block. Here is the sample code from GameScene.m:
- (void)didMoveToView:(SKView *)view
{
self.size = CGSizeMake(view.frame.size.width, view.frame.size.height);
self.backgroundColor = [UIColor whiteColor];
self.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:self.frame];
SKSpriteNode *redRect = [SKSpriteNode spriteNodeWithImageNamed:#"RedRect"];
redRect.position = CGPointMake(CGRectGetMidX(self.frame), self.frame.size.height);
redRect.physicsBody = [SKPhysicsBody bodyWithTexture:redRect.texture size:redRect.texture.size];
redRect.physicsBody.affectedByGravity = YES;
SKSpriteNode *blueRect = [SKSpriteNode spriteNodeWithImageNamed:#"BlueRect"];
blueRect.position = CGPointMake(CGRectGetMidX(self.frame), self.frame.size.height);
blueRect.physicsBody = [SKPhysicsBody bodyWithTexture:blueRect.texture size:blueRect.texture.size];
blueRect.physicsBody.affectedByGravity = YES;
[self addChild:redRect];
[self addChild:blueRect];
}
As you can see on the screenshot, there is a gap between the two blocks and between the red block and the ground. This only happens when rects collide with each other or with the ground. For instance, if I use the SKAction moveToY: which moves the block to the bottom of the screen, the gap disappears. How can I get rid of these gaps when the nodes collide?
I am totally sure that the problem is about your texture images, They are not precisely cropped or even created.
Spritekit engine never creates gaps between nodes.
Related
The nodeAtPoint: gives not the same result if using SKShapeNode and SKSpriteNode. If i am correct nodeAtPoint: will use containsPoint: to check which nodes are at the given point.
The docu states that containsPoint: will use its bounding box.
I set up a simple scene, where in situation 1 the circle is parent of the purple node and in situation 2 the green node is parent of the purple node.
I clicked in both cases in an area where the bounding box of the parent should be.
The result is differs. If i use a SKSpriteNode the nodeAtPoint: will give me the parent. If i use SKShapeNode it returns the SKScene.
(The cross marks where i pressed with the mouse.)
The code:
First setup:
-(void)didMoveToView:(SKView *)view {
self.name = #"Scene";
SKShapeNode* circle = [SKShapeNode node];
circle.path = CGPathCreateWithEllipseInRect(CGRectMake(0, 0, 50, 50), nil);
circle.position = CGPointMake(20, 20);
circle.fillColor = [SKColor redColor];
circle.name = #"circle";
SKSpriteNode* pnode = [SKSpriteNode node];
pnode.size = CGSizeMake(50, 50);
pnode.position = CGPointMake(50, 50);
pnode.color = [SKColor purpleColor];
pnode.name = #"pnode";
[self addChild: circle];
[circle addChild: pnode];
}
Second setup:
-(void)didMoveToView:(SKView *)view {
self.name = #"Scene";
SKSpriteNode* gnode = [SKSpriteNode node];
gnode.size = CGSizeMake(50, 50);
gnode.position = CGPointMake(30, 30);
gnode.color = [SKColor greenColor];
gnode.name = #"gnode";
SKSpriteNode* pnode = [SKSpriteNode node];
pnode.size = CGSizeMake(50, 50);
pnode.position = CGPointMake(30, 30);
pnode.color = [SKColor purpleColor];
pnode.name = #"pnode";
[self addChild: gnode];
[gnode addChild: pnode];
}
Call on mouse click:
-(void)mouseDown:(NSEvent *)theEvent {
CGPoint location = [theEvent locationInNode:self];
NSLog(#"%#", [self nodeAtPoint: location].name);
}
Did i miss something? Is it a bug in SpriteKit? Is it meant to work that way?
The short answers: yes, no, yes
The long answer...
The documentation for nodeAtPoint says that it
returns the deepest descendant that intersects a point
and in the Discussion section
a point is considered to be in a node if it lies inside the rectangle returned by the calculateAccumulatedFrame method
The first statement applies to SKSpriteNode and SKShapeNode nodes, while the second only applies to SKSpriteNode nodes. For SKShapeNodes, Sprite Kit ignores the node's bounding box and uses the path property to determine if a point intersects the node with CGPathContainsPoint. As shown in the figures below, shapes are selected on a per-pixel basis, where white dots represent the click points.
Figure 1. Bounding Boxes for Shape (blue) and Shape + Square (brown)
Figure 2. Results of nodeAtPoint
calculateAccumulatedFrame returns a bounding box (BB) that is relative to its parent as shown in the figure below (brown box is the square's BB). Consequently, if you don't adjust the CGPoint for containsPoint appropriately, the results will not be what you expected. To convert a point from scene coordinates to the parent's coordinates (or vice versa), use convertPoint:fromNode or convertPoint:toNode. Lastly, containsPoint uses a shape's path instead of its bounding box just like nodeAtPoint.
In the below the yellow circle does not show in the IOS emulator. If I add the setFillColor which is currently commented out, then I get a red circle.
Why does the outline of the shape not show?
Is there a way to trigger the outline?
-(void)addTargetNode2 {
float radius=90;
SKShapeNode *targetOuter = [SKShapeNode shapeNodeWithCircleOfRadius:radius];
//[targetOuter setFillColor:[UIColor redColor]];
[targetOuter setStrokeColor:[UIColor yellowColor]];
[targetOuter setLineWidth:1];
//Position the node.
targetOuter.position = CGPointMake(CGRectGetMidX(self.frame),
CGRectGetMidY(self.frame));
[self addChild:targetOuter];
}
I did find this article - but no answer.
Edit:
I've got a working work-around... but not particularly fond of it. Drawing a circle the color of the background over the top of a filled in circle:
-(void)addTarget {
float radius=50;
CGFloat borderWidth=3;
//Draw the Circle.
SKShapeNode *targetOuter = [SKShapeNode shapeNodeWithCircleOfRadius:radius];
[targetOuter setName:#"targetOuter"];
[targetOuter setFillColor:[UIColor yellowColor]];
//Following line should set the outline color but isn't working.
//[targetOuter setStrokeColor:[UIColor yellowColor]];
[targetOuter setLineWidth:1];
targetOuter.position = CGPointMake(CGRectGetMidX(self.frame),
CGRectGetMidY(self.frame));
[self addChild:targetOuter];
/* Work around
Add a circle the color of the background to emulate an outline.
Can be removed in targetOuter outline works.
*/
SKShapeNode *targetInner = [SKShapeNode shapeNodeWithCircleOfRadius:radius-borderWidth];
[targetInner setFillColor:self.backgroundColor];
targetInner.position = targetOuter.position;
[self addChild:targetInner];
}
It seems there is a bug with the iOS simulator and SKShapeNode. I've been able to replicate this problem on simulator but never on device so you shouldn't worry to add a workaround.
iOS Simulator is good to test low framerate conditions and different screen sizes but, aside from that, not very reliable to test SpriteKit games overall.
I want objects of the same category to not bounce off or be moved when they collide with one another. I've tried playing around with collisionBitMask, contactTestBitMask, mass, density, etc. Does anyone know how to do this?
self.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:self.frame.size];
self.physicsBody.affectedByGravity = NO;
self.physicsBody.allowsRotation = NO;
self.physicsBody.categoryBitMask = CollisionTypeRock;
self.physicsBody.collisionBitMask = CollisionTypeRock;
self.physicsBody.contactTestBitMask = CollisionTypeRock;
Thanks.
I am setting up an SKNode as follows:
- (id)init {
self = [super init];
if (self) {
SKTextureAtlas *atlas = [SKTextureAtlas atlasNamed:#"Sprites"];
SKSpriteNode *spriteNode = [SKSpriteNode spriteNodeWithTexture:[atlas textureNamed:#"Cat"]];
[self addChild:spriteNode];
// Create particle trail
SKEmitterNode *emitterNode = [NSKeyedUnarchiver unarchiveObjectWithFile:[[NSBundle mainBundle] pathForResource:#"Hearts" ofType:#"sks"]];
emitterNode.position = CGPointMake(0, 20);
[self insertChild:emitterNode atIndex:0];
// Setting up the physics
self.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:spriteNode.frame.size];
self.physicsBody.dynamic = YES;
self.physicsBody.affectedByGravity = NO;
}
return self;
}
When this node moves around horizontally along the X-axis, the source of the particle trail moves along with it, which is expected. What is not expected is that emitted particles do this as well, when instead I expect them to move straight up along the Y-axis from their original X position.
Is there a way to prevent this behaviour?
Creation (in scene):
SPAPlayer *player = [[SPAPlayer alloc] init];
player.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));
[self addChild:player];
Movement code:
[node.physicsBody applyForce:CGVectorMake(20 * data.acceleration.x, 0)];
Image for reference:
Make the emitter a child of the sprite (as you already have).
To make the particles move independently from where the player node is, assign the SKEmitterNode's targetNode property, preferably assign it the scene as in:
// be sure self.scene isn't still nil when you do this, for instance in node's init
emitter.targetNode = self.scene;
This will spawn the particles at the player sprite position but they won't travel along with it.
I'm using apple's Sprite Kit and I need to move a SKSprite Node in horizontal movement only. I want the rest of the physics to apply but only in the horizontal component.
Context: This is for an object supposedly on a slider that can bounce back and forth. I have everything done but if it is hit the end the wrong way it simply floats off vertically, how can I simply make it ignore all forces in the vertical direction.
By putting the node's position back at the desired Y coordinate every frame after physics has been simulated:
-(void) didSimulatePhysics
{
CGPoint pos = horizontalMoveNode.position;
pos.y = fixedVerticalPosY;
horizontalMoveNode.position = pos;
}
Add this method to your scene class and apply it to whichever node(s) you want to lock in at a given Y coordinate.
You could use constraints for this purpose. I made a short sample with a node that only moves in a fixed X range and never leaves a specified Y position:
SKSpriteNode* node = [SKSpriteNode node];
node.color = [SKColor greenColor];
node.size = CGSizeMake(20, 20);
SKRange* rangeX = [[SKRange alloc] initWithLowerLimit: 100 upperLimit: 400];
SKRange* rangeY = [SKRange rangeWithConstantValue: 100];
SKConstraint* positionConstraint = [SKConstraint positionX: rangeX Y: rangeY];
NSArray* constraintArray = [NSArray arrayWithObject: positionConstraint];
node.constraints = constraintArray;
[self addChild: node];
This method is from SKAction class to move objects only on Horizontal or X-axis:-
[mySpriteNode runAction:[SKAction moveToX:260 duration:0.5]];
I hope this work's for you.