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.
Related
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.
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.
We used below code to get sprite's collision area in cocos2d.
CGRect heroRect = [heroBird boundingBox];
if(CGRectContainsPoint(heroRect, bullet.position))
How to get boundingBox in sprite-kit?
Use the following line to get the bounding box:
CGRect heroRect = [heroBird calculateAccumulatedFrame]
see also Apple Docu
SKSpriteNode *heroBird;
heroBird = [SKSpriteNode spriteNodeWithImageNamed:#"heroBird.png"];
//heroBird rect can be set to whatever you like, shapes,rects,circles
heroBird.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:heroBird.size];
// set heroBird collision, category and contact bitmasks
// detect using didBeginContact method
CGRect heroRect = heroBird.frame;
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.
I have a Box2d body which I'm attempting to break into multiple pieces. To do this, I iterate over its fixtures and generate new bodies for each. Using debug draw, I can see that this seems to be working.
As you can see in the above image, the primary body is being broken and a secondary body (labelled: 2) is being generated. Based on the shape rendering from the debug layer, they're being represented correctly. The issue I'm having is that the CCSprite I'm associating with my primary b2body isn't being correctly positioned in reference to the new body. It seems as though the associated CCSprite is being positioned (given an anchor point of 0, 0) as if it were still part of a larger shape.
For reference, here's the code I'm using:
for (b2Fixture *f = body->GetFixtureList(); f; f = f->GetNext())
{
NSString *newSpriteFrameName = (NSString *)f->GetUserData();
// Steal some of our parent bodies properties
b2BodyDef bd;
bd.type = b2_dynamicBody;
bd.position = [self physicsPosition];
bd.angle = [self angle];
b2Body *newBody = _world->CreateBody(&bd);
b2FixtureDef fixtureDef;
fixtureDef.shape = f->GetShape();
fixtureDef.density = f->GetDensity();
fixtureDef.restitution = f->GetRestitution();
fixtureDef.friction = f->GetFriction();
fixtureDef.userData = f->GetUserData();
newBody->CreateFixture(&fixtureDef);
// Try to transfer any angular and linear velocity
b2Vec2 center1 = [self worldCenter];
b2Vec2 center2 = newBody->GetWorldCenter();
CGFloat angularVelocity = parentBody->GetAngularVelocity();
b2Vec2 velocity1 = [self linearVelocity] + b2Cross(angularVelocity, center1 - center1);
b2Vec2 velocity2 = [self linearVelocity] + b2Cross(angularVelocity, center2 - center1);
newBody->SetAngularVelocity(angularVelocity);
newBody->SetLinearVelocity(velocity2);
// Create a new destructable entity
CCSprite *newSprite = [CCSprite spriteWithSpriteFrameName:newSpriteFrameName];
SIDestructableEntity *newEntity = [[SIDestructableEntity alloc] initWithBody:newBody node:newSprite];
[[newEntity ccNode] setAnchorPoint:CGPointMake(0, 0)];
[game.entities addObject:newEntity];
[game.entityLayer addChild:[newEntity ccNode]];
}
Here's how I'm setting my CCSprites location each logic tick:
b2Vec2 position = body->GetPosition();
ccNode.position = CGPointMake(PTM_RATIO*position.x, PTM_RATIO*position.y);
ccNode.rotation = -1 * CC_RADIANS_TO_DEGREES(body->GetAngle());
This line looks suspicious.
[[newEntity ccNode] setAnchorPoint:CGPointMake(0, 0)];
The sprite usually has an anchor point of (0.5,0.5). If your body's anchor point is in the middle (can't tell from code above), then an anchor of (0.5,0.5) for the sprite would put it in the middle as well. Putting it at (0,0) puts the sprite's top left corner at the position of the sprite.
My guess is that your body anchor is at the bottom left of body and the sprite anchor is at the top right, giving the effect you are seeing.