Drawing Trail Lines for SKSpriteNode - sprite-kit

I have a custom LineBall class as shown below:
#implementation LineBall
-(instancetype) init {
self = [super initWithImageNamed:LINE_BALL_IMAGE];
self.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:self.size.width/2];
self.physicsBody.categoryBitMask = BBPhysicsCategoryLineBall;
self.name = #"lineBall";
self.physicsBody.friction = 0.0f;
self.physicsBody.restitution = 1.0f;
self.physicsBody.linearDamping = 0.0f;
self.physicsBody.allowsRotation = NO;
self.physicsBody.dynamic = YES;
return self;
}
Later on I add this to the GameScene and it works as expected. The problem is that now I want to draw lines wherever the LineBall travels. How can I do that?

Here's one way to draw a trail behind a moving ball.
In the didSimulatePhysics method:
Store the ball's positions over time in a mutable array. Insert at index = 0.
Remove the oldest (last) array element if the number of elements exceeds the max trail size
Create a mutable CGPath by looping over the elements of the array using CGPathAddLineToPoint to connect each point
Remove the old trail from the scene if it exist
Create the ball's trail by creating an SKShapeNode from the path using shapeNodeWithPath
Add the SKShapeNode to the scene

Related

SKNode nodeAtPoint: / containsPoint: not the same behaviour for SKSpriteNode and SKShapeNode

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.

Move SKSpriteNode with Physics

I am trying to move an SKSpriteNode with physics by setting it's velocity. I am doing this on top of another SKNode (the map) that is part of the SKScene. Positions of "cities" are stored in custom Location objects.
I know the location of the initial SKSpriteNode (the ship) and I know the desired location. From what I've read, I can move the sprite by setting it's velocity. I do so like this:
float dy = _player.desiredLocation.yCoordinate - _mapNode.pin.position.y;
float dx = _player.desiredLocation.xCoordinate - _mapNode.pin.position.x;
_mapNode.pin.physicsBody.velocity = CGVectorMake(dx, dy);
This is all inside the didSimulatePhysics function inside the SKScene. Once the user taps a Location, I find the position and set the velocity. This seems to work well the FIRST time, but the sprite moves all over the place in subsequent times. Any idea what could be going wrong here?
PS: Setting skView.showsPhysics = YES; puts the circle of the physics body way off the position of the sprite.
In the map node:
self.pin = [SKSpriteNode spriteNodeWithImageNamed:[IPGameManager sharedGameData].world.player.ship.type];
self.pin.userInteractionEnabled = NO;
self.pin.position = CGPointMake([IPGameManager sharedGameData].world.player.location.xCoordinate, [IPGameManager sharedGameData].world.player.location.yCoordinate);
self.pin.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:10.0];
self.pin.physicsBody.dynamic = YES;
self.pin.physicsBody.allowsRotation = NO;
self.pin.physicsBody.friction = 0.0;
self.pin.physicsBody.linearDamping = 0.0;
[self addChild:self.pin];

Emitted particles follow parent node

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.

Multiple sprites with same SKPhysics body object

I am building an airport simulation game using sprite kit. My layout for the game states intact before adding SKPhysics body and once SKPhysicsbody is set for nodes, my sprite nodes goes wary.
This is what I am adding to scene without SKPhysicsBody. Imagine a airport with many gates and flights standing next to the gates. That is what I am trying to achieve with below code.
#implementation MyScene
-(id)initWithSize:(CGSize)size
{
if (self = [super initWithSize:size])
{
allPlanes = [[NSMutableArray alloc]init];
// setting gravity to 0
[[self physicsWorld] setGravity:CGVectorMake(0, 0)];
[[self physicsWorld] setContactDelegate:self];
[self setBackgroundColor:[UIColor whiteColor]];
// Below method will provide runway and other static objects that can be seen in an airport
[self setupAirport];
/* This is where I am setting up by plane sprites.This method will provide a node which is added to the scene. As you can notice, the sprites are added at specific coordinates until they fill the screen. Imagine three or four gates with flights standing by those gates. That is what I am trying to achieve with below while loop */
int xval = 20;
while (xval < kScreenWidth)
{
[self setupFlightAtPoint:xval];
xval = xval + 60;
}
}
return self;
}
Now code for methods [self setupFlightAtPoint:xPos]
-(void) setupFlightAtPoint:(CGFloat ) xPos
{
// Below code will provide a static gate like object.gateNode is of type Gate class which is a subclass of SKNode
gateNode = [[Gate node] newGate];
[gateNode setPosition:CGPointMake(xPos, kScreenHeight * 0.37)];
[self addChild:gateNode];
// Below code provide plane node and positions it near the gate object.Plane is subclass of SKNode
Plane *plane = [[Plane alloc]init];
imageNode = [plane newPlane];
imageNode.planeIdentifier = xPos;
[imageNode setPosition:CGPointMake(gateNode.frame.origin.x + 12, gateNode.frame.origin.y+15)];
[allPlanes addObject:imageNode];
[self addChild:imageNode];
}
Plane object method
-(instancetype) newPlane
{
[self setScale:0.10];
SKSpriteNode *spriteNode = [SKSpriteNode spriteNodeWithImageNamed:#"plane.png"];
[self addChild:spriteNode];
return self;
}
Till now everything looks fine.Please see attached image called scene1 to see what I see with above code.
Now my problem begins here when I am trying to set physics body to my plane sprites. In my "newPlane" method, I am adding below code
-(instancetype) newPlane
{
[self setScale:0.10];
SKSpriteNode *spriteNode = [SKSpriteNode spriteNodeWithImageNamed:#"plane.png"];
[self addChild:spriteNode];
SKPhysicsBody *planePhysics = [SKPhysicsBody bodyWithRectangleOfSize:spriteNode.frame.size];
[spriteNode setPhysicsBody:planePhysics];
[[spriteNode physicsBody] setAffectedByGravity:NO];
return self;
}
After setting Physicsbodies, my scene looks like this
only one plane sprite is seen in my scene now and I am not able to figure out why?
Try initializing and assigning the physics body before adding the sprite as child:
-(instancetype) newPlane
{
[self setScale:0.10];
SKSpriteNode *spriteNode = [SKSpriteNode spriteNodeWithImageNamed:#"plane.png"];
spriteNode.position = CGPointMake(100, 200);
SKPhysicsBody *planePhysics = [SKPhysicsBody bodyWithRectangleOfSize:spriteNode.frame.size];
spriteNode.physicsBody = planePhysics;
spriteNode.physicsBody.affectedByGravity = NO;
[self addChild:spriteNode];
return self;
}
I also converted the code to use dot notation, I find this easier to type and read.

SpriteKit 1 Dimensional Movement

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.