Draw the Path of a Moving SKSpritenode - sprite-kit

I am Trying to use the new SKSpritenode , i have managed to create a Spritenode move it around the screen , although i want the Spritenode leaves a trace( color ) where it moves.
the code to create the Sprite node & my attempts to create a shapenode as a child for the spritenode ( which did not work ) .
-(void)movetherayoflight{
ray1=[[lightray1 alloc]initWithColor:[SKColor redColor] size:CGSizeMake(6,6)];
[ray1 setPosition:CGPointMake(50, 50)];
ray1.physicsBody=[SKPhysicsBody bodyWithRectangleOfSize:ray1.size];
ray1.physicsBody.restitution=1;
ray1.physicsBody.linearDamping=0;
ray1.physicsBody.friction=0;
ray1.physicsBody.allowsRotation=NO;
ray1.physicsBody.velocity=CGVectorMake(0.0f, 300.0f);
[self addChild:ray1];
SKShapeNode *raayyy=[[SKShapeNode alloc]init];
CGMutablePathRef rayPath = CGPathCreateMutable();
CGPoint fff=CGPointMake(ray1.position.x, ray1.position.y);
CGPoint startpoint=CGPointMake(50, 100);
//CGPathAddLines(rayPath, NULL, &fff, 2);
CGPathAddLines(rayPath, NULL, &startpoint, 5);
//CGPathAddLineToPoint(rayPath, NULL, ray1.position.x, ray1.position.y);
raayyy.path=rayPath;
raayyy.lineWidth = 1.0;
//raayyy.fillColor = [SKColor whiteColor];
raayyy.strokeColor = [SKColor greenColor];
raayyy.glowWidth = 0.5;
[ray1 addChild:raayyy];
}
If you have better Solution , Please let me know !

Instead of making the SKShapeNode as the child of the SKSpriteNode, declare it as its sibling in the SKScene.
First, declare the SKShapeNode and the CGPath as instance variables.
In your scene's -initWithSize: method,
raayyy=[[SKShapeNode alloc]init];
//Additional initialization here.
rayPath = CGPathCreateMutable();
CGPathMoveToPoint(pathToDraw, NULL, ray1.position.x, ray1.position.y);
rayyy.path = rayPath;
[self addChild:rayyy];
Then in your -update: method,
CGPathAddLineToPoint(rayPath, NULL, yar1.position.x, ray1.position.y);
rayyy.path = rayPath;
This is just a suggestion, I haven't tried anything of the like myself.

well , I have managed it with a solution but i really dont like it so far
-(SKShapeNode*)gravityline{
SKShapeNode *lolo = [[SKShapeNode alloc] init];
CGPoint fff=CGPointMake(ray1.position.x, ray1.position.y);
CGMutablePathRef path = CGPathCreateMutable();
CGPathMoveToPoint(path, NULL, fff.x, fff.y);
CGPathAddLineToPoint(path, 0,rayoriginpoint.x,rayoriginpoint.y );
CGPathCloseSubpath(path);
lolo.path = path;
lolo.name=#"gravityline";
lolo.strokeColor=[SKColor greenColor];
lolo.glowWidth=.1;
lolo.physicsBody=[SKPhysicsBody bodyWithPolygonFromPath:path];
lolo.physicsBody.categoryBitMask=raylightCategory;
lolo.physicsBody.collisionBitMask=batCategory;
lolo.physicsBody.contactTestBitMask=batCategory;
lolo.physicsBody.dynamic=NO;
CGPathRelease(path);
return lolo;
}
The rayoriginpointchanges its value once the ray point hits something
if (firstBody.categoryBitMask == rayCategory && secondBody.categoryBitMask==mirrorCategory )
{
[self addChild:[self gravityline]];
CGPoint p = contact.contactPoint;
rayoriginpoint=p;
NSLog(#"Contact have been made");
}
What i want to do is very basic :
1- Either to change the Color of every CGPOINT that the SKSpritenode Passes by
2- Either to create Sprite node that scales up in certain direction till it hits something then changes direction

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.

Drawing line starting from SKSpriteNode_01.pos - ending at SKSpriteNode_02.pos (and adding particles)

Hi All I am wondering what is the best approach to draw a particlesystem line between two sprites (which updates if the sprite positions moved nudged/gravity etc)
Ive tried this way (ps sorry for my newbie code ><)
drawing the line starting from obj01 - ending at obj02
that is working so far but I im not sure how to update the line coordinates if the sprites move for example
the other problem related probably because the pathToDraw is not updated the particle system seems to drift off
So I know I need to update it (if someone could write a sudo code/idea of where I would expect to init the path to draw, where to remove etc that would really be very helpful in figuring out)
with thanks N :)
-(void)drawLineFromObj2:(EnemyClass*)obj03 to:(EnemyClass*)obj04
{
if (UFO02IsDead == NO && UFO03IsDead == NO)
{
if (ufo_02ReadyToLink == YES && ufo_03ReadyToLink == YES) {
NSLog(#"Line from Obj2 to obj03");
// **CreateLine**
lineNode02.path = pathToDraw;
lineNode02 = [SKShapeNode node];
CGMutablePathRef pathToDraw = CGPathCreateMutable();
CGPathMoveToPoint(pathToDraw, NULL, obj03.position.x, obj03.position.y);
CGPathAddLineToPoint(pathToDraw, NULL, obj04.position.x, obj04.position.y);
lineNode02.path = pathToDraw;
//Add Particles
NSString *myParticlePath = [[NSBundle mainBundle] pathForResource:#"ForceField" ofType:#"sks"];
SKEmitterNode *myParticle = [NSKeyedUnarchiver unarchiveObjectWithFile:myParticlePath];
[self addChild:myParticle];
//get particles to drop by adding physics?
(this no effect the particles don't follow the line and don't seem
attached to it, and if the sprites move they dot change position either)
//myParticle.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:self.frame.size];
//myParticle.physicsBody.affectedByGravity = YES;
//myParticle.physicsBody.allowsRotation = NO;
pathToDraw
myParticle uses SKAction followPath:pathToDraw
How to update pathToDraw to draw smooth lines and remove last line properly
SKAction *followTrack =
[SKAction followPath:pathToDraw asOffset:NO orientToPath:YES duration:.5];
SKAction *forever = [SKAction repeatActionForever:followTrack];
myParticle.particleAction = forever;
lineNode02.name = #"lineNode";
[self addChild:lineNode02];
//is there a way to link the movement with the crystal positions and animate the line length and angle (is there another way of doing this)
//stops line being redrawn (but how to update when sprite is moved, and how to delete the old lines?)
ufo_03ReadyToLink = NO;
}
}
}
Fixed it by moving the function down into
-(void)update
created an action block
SKAction *remove = [SKAction removeFromParent];
SKAction* blockAction = [SKAction runBlock:^
{
[self DrawLine]; //draws new line every update
//then removes line
}];
[self runAction:[SKAction sequence:#[blockAction,remove]]];
also used a texture rather than a particle but this is how I attached it
//Add Particles
NSString *myParticlePath =
[[NSBundle mainBundle] pathForResource:#"ForceField" ofType:#"sks"];
myParticle = [NSKeyedUnarchiver unarchiveObjectWithFile:myParticlePath];
[self addChild:myParticle];
SKAction *followTrack =
[SKAction followPath:pathToDraw asOffset:NO orientToPath:YES duration:.5];
SKAction *forever = [SKAction repeatActionForever:followTrack];
myParticle.particleAction = forever;
lineNode01.name = #"lineNode";
[self addChild:lineNode01];
Hope it helps someone else :)

myParticleEmitter weirdness (creates artifact at 0.0) while drawing from point A to point B

A little weirdness here, I have a particle emitter added to an SKShapeNode
(the particles should follow the line)
yet when it does it also has a bit of particle system sitting at 0,0
Ive moved it up a little so you can see it better
Any thoughts to how this might be happening?,
I'll post the code below, but still I cant find anything, I have tested with other textures setting setStrokeTexture - incase the line is making some sort of sneaky extra part - but no it seems fine....and creates a line between the sprites...
in LinkWithNumberClass //SKSpriteNode SKPhysicsContactDelegate
if (isUsingParticles == YES) {
//create new line
SKShapeNode*lineNode02 = [SKShapeNode node];
lineNode02.lineWidth = 10;
lineNode02.name = #"lineNode";
//clear line of colour
lineNode02.fillColor = [SKColor clearColor];
lineNode02.strokeColor = [SKColor clearColor];
//create the path the line follows (line joins two sprites)
CGMutablePathRef pathToDraw = CGPathCreateMutable();
CGPathMoveToPoint(pathToDraw, NULL, spriteA.position.x, spriteA.position.y);
CGPathAddLineToPoint(pathToDraw, NULL, spriteB.position.x, spriteB.position.y);
lineNode02.path = pathToDraw;
[self addChild:lineNode02];
//Add Particles
myParticlePath = [[NSBundle mainBundle] pathForResource:_parName ofType:#"sks"];
myParticleEmitter = [NSKeyedUnarchiver unarchiveObjectWithFile:myParticlePath];
[self addChild:myParticleEmitter];
CGFloat distancebtween = SDistanceBetweenPoints(spriteA.position, spriteB.position);
myParticleEmitter.particlePositionRange = CGVectorMake( particleLength,0 );
myParticleEmitter.position = CGPointMake(distancebtween/2, +10);
self->myParticleEmitter.targetNode = self.scene;
int particleTime = 3;
SKAction *followTrack =
[SKAction followPath:pathToDraw
asOffset:YES
orientToPath:YES
duration:particleTime];
SKAction *forever = [SKAction repeatActionForever:followTrack];
self->myParticleEmitter.particleAction = forever;
}
In Game Layer
-(void)addStaticLinkedSpriteWithParticles
{
twoSpritesWithParticlesBridge =
[[LinkWithNumber alloc]initWithlinkSpriteA:#"Object"
spriteB:#"Object"
andPlistAnimation:#"Aniamtions"
distbetween:100
hasParticles:YES
ParticlesNamed:#"Fire"];
[self addChild:self->twoSpritesWithParticlesBridge];
}
With thanks :)
Natalie.
In the end I go it to work not needing to draw a line at all.
Create particle system Position on SpriteA
find distance between
the two sprites (to get length for particles)
Adjust length of the particles in emitter properties
reposition the
particles so they are centered between the two sprites
if (isUsingParticles == YES) {
//create particles
myParticlePath = [[NSBundle mainBundle] pathForResource:_parName ofType:#"sks"];
myParticleEmitter = [NSKeyedUnarchiver unarchiveObjectWithFile:myParticlePath];
[self addChild:myParticleEmitter];//try moving it to the line node
//find length needed for particles
CGFloat distancebtween = SDistanceBetweenPoints(spriteA.position, spriteB.position);
//Adjust length of particles
myParticleEmitter.particlePositionRange = CGVectorMake( particleLength,0 );
//center particles between the two sprites
myParticleEmitter.position = CGPointMake(spriteA.position.x+distancebetween/2, spriteA.position.y);
myParticleEmitter.targetNode = self.scene;
}
Hope it helps someone else :)

how to make Sprite follow bezier curve

Im fairly new to objective-c and sprite kit but i've done games development for a while. Im currently working on a 2d game and i have enemy ships moving from right to left on the screen. Ive been following tutorials for different parts of my game and then adding to it where necessary. I found a tutorial where the in game enemies follow a bezier path and i've managed to implement this in my game however as i'm new to bezier curves i do not fully understand them and the algorithm makes my sprites move from top to bottom but i need them to go left to right.
I have included the code snippet that i have used to add the bezier curve to my sprites any suggestions on how i can have them move right to left instead of top to bottom.
CGMutablePathRef cgpath = CGPathCreateMutable();
float xStart = [self getRandomNumberBetween:0+asteroid.size.width to:self.frame.size.width-asteroid.size.width];
float xEnd = [self getRandomNumberBetween:0+asteroid.size.width to:self.frame.size.width-asteroid.size.width];
float cp1X =[self getRandomNumberBetween:0+asteroid.size.width to:self.frame.size.width-asteroid.size.width];
float cp1y = [self getRandomNumberBetween:0+asteroid.size.width to:self.frame.size.width-asteroid.size.height];
float cp2x = [self getRandomNumberBetween:0+asteroid.size.width to:self.frame.size.width-asteroid.size.width];
float cp2Y = [self getRandomNumberBetween:0 to:cp1y];
CGPoint s = CGPointMake(xStart, 1024.0);
CGPoint e = CGPointMake(xEnd, -100);
CGPoint cp1 = CGPointMake(cp1X, cp1y);
CGPoint cp2 = CGPointMake(cp2x, cp2Y);
CGPathMoveToPoint(cgpath, NULL, s.x, s.y);
CGPathAddCurveToPoint(cgpath, NULL, cp1.x, cp1.y, cp2.x, cp2.y, e.x, e.y);
SKAction *enemyCurve = [SKAction followPath:cgpath asOffset:NO orientToPath:YES duration:5];
CGPoint location = CGPointMake(-self.frame.size.width-asteroid.size.width, randY);
SKAction *moveAction = [SKAction moveTo:location duration:randDuration];
SKAction *doneAction = [SKAction runBlock:(dispatch_block_t)^(){
asteroid.hidden = YES;
}];
SKAction *moveAsteroidActionWithDone = [SKAction sequence:#[enemyCurve,moveAction, doneAction]];
Thank you for any help and suggestions.
Bezier curves are used to generate a smooth curve between two points.
To move the path from left to right, choose a starting point on the left and choosing an ending point on the right. The two control points determine the shape of the path to take from left to right. Varying the startpoint and endpoint in the following code will control where the bezier curve starts and where it ends. Varying the control points vary the shape of the curve. You can see that with the attached GIF.
CGMutablePathRef cgpath = CGPathCreateMutable();
CGPoint startingPoint = CGPointMake(50, 100);
CGPoint controlPoint1 = CGPointMake(160, 250);
CGPoint controlPoint2 = CGPointMake(200, 140);
CGPoint endingPoint = CGPointMake(303, 100);
CGPathMoveToPoint(cgpath, NULL, startingPoint.x, startingPoint.y);
CGPathAddCurveToPoint(cgpath, NULL, controlPoint1.x, controlPoint1.y,
controlPoint2.x, controlPoint2.y,
endingPoint.x, endingPoint.y);
SKAction *enemyCurve = [SKAction followPath:cgpath asOffset:NO orientToPath:YES duration:5];
[enemy runAction:[SKAction sequence:#[[SKAction waitForDuration:1],enemyCurve]]];
P0 and P3 are the starting and ending points and P1 and P2 are the control points.
Check out this page to play more with bezier curves.
http://www.jasondavies.com/animated-bezier/

colorizeWithColor and SKLabelNode

The SKAction colorizeWithColor: does as per the docs only work with SKSpriteNode, so what do we do with SKLabelNode? SKLabelNode does have both color and colorBlendFactor properties that can be set statically. Is there some way to animate this with SKAction?
My current approach is to render a SKLabelNode to a texture using SKView's instance method textureFromNode, but just get nil texture out of that atm :-(
Update: What do you know. I think I found out the problem with the texture rendering. It's not possible to redner a texture in the init method of SKScene, because self.view is nil at that point. So I tried it in didMoveToView and voila, texture rendered. Thanks anyway :-)
Maybe somebody will find this useful.
func changeColorForLabelNode(labelNode: SKLabelNode, toColor: SKColor, withDuration: NSTimeInterval) {
labelNode.runAction(SKAction.customActionWithDuration(withDuration, actionBlock: {
node, elapsedTime in
let label = node as SKLabelNode
let toColorComponents = CGColorGetComponents(toColor.CGColor)
let fromColorComponents = CGColorGetComponents(label.fontColor.CGColor)
let finalRed = fromColorComponents[0] + (toColorComponents[0] - fromColorComponents[0])*CGFloat(elapsedTime / CGFloat(withDuration))
let finalGreen = fromColorComponents[1] + (toColorComponents[1] - fromColorComponents[1])*CGFloat(elapsedTime / CGFloat(withDuration))
let finalBlue = fromColorComponents[2] + (toColorComponents[2] - fromColorComponents[2])*CGFloat(elapsedTime / CGFloat(withDuration))
let finalAlpha = fromColorComponents[3] + (toColorComponents[3] - fromColorComponents[3])*CGFloat(elapsedTime / CGFloat(withDuration))
labelNode.fontColor = SKColor(red: finalRed, green: finalGreen, blue: finalBlue, alpha: finalAlpha)
}))
}
You can even check the result of this function in use in this demo: https://www.youtube.com/watch?v=ZIz8Bn0-hUA&feature=youtu.be
I decided to write here a short nice solution, but if you need more details, check this question: SKAction.colorizeWithColor makes SKLabelNode disappear
SKLabelNode also has a fontColor property that you can set. However, it does not respond to the colorizeWithColor method.
Yet, you can still get it to dynamically change the color of the text by syncing it with another SKSpriteNode color. If you call colorizeWithColor on your sprite, the font color changes with it. And that includes color transitions over the specified duration. Example:
[_tileCountLabel runAction:[SKAction repeatActionForever:
[SKAction customActionWithDuration:COLOR_TRANSITION_SPEED actionBlock:^(SKNode *node, CGFloat elapsedTime) {
_tileCountLabel.fontColor = _tileLayer0.color;
}]]];
Also, I tried using SKCropNode mask to try setting the font color through a parent SKSpriteNode. The colorizeWithColor method on the SKSpriteNode worked, but the font was badly mangled and chunky. So, not useful.
So here is the SKTexture/SKSpriteNode workaround solution. It could maybe be wrapped in its own class for ease of use. And the one thing to remember is to render this where self.view is not nil...
SKLabelNode *labNode = [SKLabelNode labelNodeWithFontNamed:FONT_GAME];
labNode.fontSize = 30.0f;
labNode.fontColor = [SKColor whiteColor];
labNode.text = #"TEST";
SKTexture *texture;
NSAssert(self.view != nil, #"Can't access self.view so sorry.");
texture = [self.view textureFromNode:labNode];
DLog(#"texture: %#", texture);
if (texture != nil) {
texture.filteringMode = SKTextureFilteringNearest;
SKSpriteNode *spriteText = [SKSpriteNode spriteNodeWithTexture:texture];
DLog(#"spriteText.size: %#", NSStringFromSize(spriteText.size));
spriteText.anchorPoint = ccp(0, 0.5);
spriteText.position = ccp(0, 25);
[self addChild:spriteText];
[spriteText runAction:[SKAction repeatActionForever:[SKAction sequence:#[
[SKAction colorizeWithColor:SKColor.yellowColor colorBlendFactor:1 duration:1],
[SKAction colorizeWithColor:SKColor.yellowColor colorBlendFactor:0 duration:1],
]]]];
}
else {
DLog(#"Texture is nil!");
}
If you want to colorize an SKLabelNode, you could try using the label as a mask and colorizing the background:
SKSpriteNode *background = [SKSpriteNode spriteNodeWithColor:[UIColor whiteColor] size:CGSizeMake(200.0, 200.0)];
SKAction *turnRed = [SKAction colorizeWithColor:[UIColor redColor] colorBlendFactor:1.0 duration:2.0];
SKCropNode *cropNode = [SKCropNode node];
cropNode.maskNode = [yourAwesomeLabel copy];
[cropNode addChild:background];
[background runAction:turnRed];
[self addChild:cropNode];