I have created a particle emitter in Xcode that has quite a lengthy trail. When i move it inside of the particle generator it leaves a trail following my mouse path.
some background info on my goal:
In my spriteKit game the user drags their finger around the screen to shoot moving objects. I am attempting to create a "Bullet Time" effect where the objects slow down and highlights when the current finger location touches them. When the finger stops moving or they run out of ammo the touchesEnded method is fired shooting all of the highlighted objects. Currently I have the path that they travel showing up as a line drawn to the screen using SKShapeNode & CGPath, but I would like the trail to be highlighted with the emitter trail instead.
In the touches began method I create a circle SpriteNode that moves around wherever the finger moves to (the physics collision is attached to the circle not the path). I've attached the particle trail emitter to this circle and it moves with the circle when I move it around the screen, but does not leave a trail.
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint touchLocation = [touch locationInNode:self];
pathToDraw = CGPathCreateMutable();
startPoint = touchLocation;
CGPathMoveToPoint(pathToDraw, NULL, touchLocation.x, touchLocation.y);
lineNode = [SKShapeNode node];
lineNode.path = pathToDraw;
lineNode.lineWidth = 2;
lineNode.zPosition = 110;
lineNode.strokeColor = [SKColor whiteColor];
[self addChild:lineNode];
circle = [SKSpriteNode spriteNodeWithTexture:[animationsAtlas textureNamed:#"anim_countdown_9"]];
circle.name = #"circle";
circle.scale = 0.3;
circle.alpha = 0.5;
circle.zPosition = 110;
circle.position = touchLocation;
circle.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:20];
circle.physicsBody.categoryBitMask = weaponCategory;
circle.physicsBody.dynamic = NO;
circle.physicsBody.contactTestBitMask = billyCategory;
circle.physicsBody.collisionBitMask = 0;
[self addChild:circle];
pathEmitter = [SKEmitterNode skt_emitterNamed:#"FollowPath"];
//pathEmitter.position = circle.position;
//pathEmitter.targetNode = self;
//pathEmitter.scale = 0.2;
//pathEmitter.zPosition = 60;
[circle addChild:pathEmitter];
}
In touchesMoved method I move the circle accordingly to the new position
- (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event {
UITouch *touch = [touches anyObject];
CGPoint currentPoint = [touch locationInNode:self];
circle.position = currentPoint;
SKAction *wtf = [SKAction followPath:pathToDraw asOffset:NO orientToPath:YES duration:0.1];
//pathEmitter.position = currentPoint;
//[pathEmitter runAction:wtf];
//pathEmitter.particleAction = wtf;
pathEmitter.particleAction = [SKAction moveTo:currentPoint duration:1.0];
CGPathAddLineToPoint(pathToDraw, NULL, currentPoint.x, currentPoint.y);
lineNode.path = pathToDraw;
I've tried setting pathEmitter.targetNode = self; like this post Making a particle follow a path in spriteKit suggests but then the emitter doesn't appear at all.
and if i set the particleAction to followPath it does not leave a trail either.
In my code you can see I've commented out some lines, basically I've tried every combination of targetNode & particleAction I can think of.
Any suggestions on how I can get the emitter to leave a trail on my finger path?
thanks
actually pathEmitter.targetNode = self; is the one that will let you have a particle to leave a trail as your object moves, I'm not seeing any reason why it's not working for you 'cause I've been using this method for like a long time ago now, check your position specially for method touchesMoved
I think this code is all what you need;
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint touchLocation = [touch locationInNode:self];
circle = [SKSpriteNode spriteNodeWithTexture:[animationsAtlas textureNamed:#"anim_countdown_9"]];
circle.name = #"circle";
circle.scale = 0.3;
circle.alpha = 0.5;
circle.zPosition = 110;
circle.position = touchLocation;
circle.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:20];
circle.physicsBody.categoryBitMask = weaponCategory;
circle.physicsBody.dynamic = NO;
circle.physicsBody.contactTestBitMask = billyCategory;
circle.physicsBody.collisionBitMask = 0;
[self addChild:circle];
pathEmitter = [NSKeyedUnarchiver unarchiveObjectWithFile: [[NSBundle mainBundle] pathForResource:#"FollowPath"ofType:#"sks"]];
pathEmitter.position = CGPointMake(0, -60);
[circle addChild:pathEmitter];
}
- (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event {
UITouch *touch = [touches anyObject];
CGPoint currentPoint = [touch locationInNode:self];
circle.position = currentPoint;
}
- (void)update:(CFTimeInterval)delta
{
if (!pathEmitter.targetNode) {
pathEmitter.targetNode = self;
}
}
Related
I've created a NSUInteger that increments per pixel moved in Touches Moved method. However the increment is not consistent. It appears that moving your finger fast will increase the number slowly whereas moving your finger slowly will increase the number fast.
Essentially I'm creating a ink bottle effect, so that drawing on the screen will decrease the ink in line with finger movement, but with different speeds of drawing, this wont be always the same.
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch* touch = [touches anyObject];
CGPoint positionInScene = [touch locationInNode:self];
SKLabelNode *touchedNode = (SKLabelNode *)[self nodeAtPoint:positionInScene];
pathToDraw = CGPathCreateMutable();
CGPathMoveToPoint(pathToDraw, NULL, positionInScene.x, positionInScene.y);
lineNode = [[SKShapeNode alloc] init];
lineNode.path = pathToDraw;
[_gameNode addChild:lineNode];
}
- (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event
{
UITouch* touch = [touches anyObject];
CGPoint positionInScene = [touch locationInNode:self];
CGPathAddLineToPoint(pathToDraw, NULL, positionInScene.x, positionInScene.y);
lineNode.path = pathToDraw;
lineNode.name = lineNodeCategoryName;
lineNode.physicsBody = [SKPhysicsBody bodyWithEdgeChainFromPath:pathToDraw];
lineNode.physicsBody.categoryBitMask = lineNodeCategory;
lineNode.physicsBody.contactTestBitMask = bubble1Category|bubble2Category|bubble3Category|bubble4Category|bubble5Category;
lineNode.physicsBody.collisionBitMask = ballCategory;
lineNode.physicsBody.dynamic = YES;
lineNode.strokeColor = [SKColor blackColor];
lineNode.glowWidth = 3.0;
testNumber ++;
testLabel.text = [NSString stringWithFormat:#"%lu",(unsigned long)testNumber];
}
touchesMoved:withEvent: is called when a move event is detected. You won't be able to rely on the number of times this method is called to detect how far the user's finger moved.
You should rather try to rely on the distance the finger has travelled between two events to increment your test number.
I am working on a application and i am drawing line based on user touch of user finger. Once the touch end event received the line is converted to last path. A new line is draw with name "Current path" node when a new touch began event received. I added a physics body for both the line with opposite contact bit mask but i am not able to receive collision event.
Following is my code:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch* touch = [touches anyObject];
CGPoint positionInScene = [touch locationInNode:self];
currentPath = CGPathCreateMutable();
currentPathNode = [self newLineNodeWithFillColor : CURRENT_LINE_COLOR];
CGPathMoveToPoint(currentPath, NULL, positionInScene.x, positionInScene.y);
currentPathNode.path = currentPath;
[self addChild:currentPathNode];
uint32_t contactBitMask = circleCategory | lastPathCategory;
[self addPhysicsBodyForLine:currentPathNode withCategoryBitMask:drawPathCategory withContactBitMask:contactBitMask];
}
- (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event
{
UITouch* touch = [touches anyObject];
CGPoint positionInScene = [touch locationInNode:self];
CGPathAddLineToPoint(currentPath, NULL, positionInScene.x, positionInScene.y);
currentPathNode.path = currentPath;
uint32_t contactBitMask = lastPathCategory;
[self addPhysicsBodyForLine:currentPathNode withCategoryBitMask:drawPathCategory withContactBitMask:contactBitMask];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
if(lastPath == nil){
lastPath = CGPathCreateMutable();
}
CGPathAddPath(lastPath, nil, currentPath);
[lastPathNode removeFromParent];
if(currentPathNode != nil){
[currentPathNode removeFromParent];
currentPathNode = nil;
}
lastPathNode = [self newLineNodeWithFillColor : LAST_LINE_COLOR];
lastPathNode.path = lastPath;
[self addChild:lastPathNode];
[self addPhysicsBodyForLine:lastPathNode withCategoryBitMask:lastPathCategory withContactBitMask:drawPathCategory];
CGPathRelease(currentPath);
}
- (void) addPhysicsBodyForLine:(SKShapeNode*)node withCategoryBitMask:(uint32_t)category withContactBitMask:(uint32_t)contactBitMask{
node.physicsBody = [SKPhysicsBody bodyWithEdgeChainFromPath:node.path];
node.physicsBody.categoryBitMask = category;
node.physicsBody.contactTestBitMask = contactBitMask;
node.physicsBody.collisionBitMask = contactBitMask;
node.physicsBody.dynamic = YES;
node.physicsBody.usesPreciseCollisionDetection = YES;
}
But collision is not Detected? Any Solution.
Collisions do not work that way. You will only register a collision if you use physics to move a node's position. Creating a new physics body over (or across) an already existing physics body will not register a collision.
You can use -(BOOL)intersectsNode:(SKNode *)node every time a new path is drawn to check if the new node intersects any other node.
I am trying to create an SKPhysicsBody from a line drawn in the scene. This line is not a closed polygon, but simply created while tracking the users finger in touchesMoved. I need to create a physics body out of this line so that a ball bounces off of it as if it were a ledge, but I can't seem to figure out how to do that.
The closest thing I've found is bodyWithPolygonFromPath, but it only works when the line has been closed into a polygon.
I'm very new to iOS programming (this is my first project) so please take it easy!
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[lineNode removeFromParent];
CGPathRelease(pathToDraw);
UITouch* touch = [touches anyObject];
CGPoint positionInScene = [touch locationInNode:self];
pathToDraw = CGPathCreateMutable();
CGPathMoveToPoint(pathToDraw, NULL, positionInScene.x, positionInScene.y);
lineNode = [SKShapeNode node];
lineNode.path = pathToDraw;
lineNode.strokeColor = [SKColor redColor];
[self addChild:lineNode];
}
- (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event
{
UITouch* touch = [touches anyObject];
CGPoint positionInScene = [touch locationInNode:self];
CGPathAddLineToPoint(pathToDraw, NULL, positionInScene.x, positionInScene.y);
lineNode.path = pathToDraw;
}
- (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event
{
lineNode.physicsBody = [SKPhysicsBody bodyWithPolygonFromPath:lineNode.path];
lineNode.physicsBody.categoryBitMask = paddleCategory;
lineNode.physicsBody.collisionBitMask = ballCategory | paddleCategory;
lineNode.physicsBody.contactTestBitMask = ballCategory | paddleCategory;
}
Does the line need to move or react to collisions, or just cause collisions?
If the former, you need a dynamic body — and a dynamic body has to have volume (well, area) so an open path won't work. In this case you could either loop the path back along itself to close it (probably easy to code but hard to make work right) or approximate the path with a collection of narrow rectangular bodies.
If the latter, a static body will do. You can create one from a line segment between two points with bodyWithEdgeFromPoint:toPoint:, or from a series of segments with bodyWithEdgeChainFromPath:.
Im using the following code to draw lines in my sprite kit scene but nothing shows up. But the node counts goes up. Can anyone see what could be the problem. Ive been fighting with this code forever
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch* touch = [touches anyObject];
CGPoint positionInScene = [touch locationInNode:self];
pathToDraw = CGPathCreateMutable();
CGPathMoveToPoint(pathToDraw, NULL, positionInScene.x, positionInScene.y);
lineNode = [SKShapeNode node];
lineNode.path = pathToDraw;
lineNode.strokeColor = [SKColor redColor];
//lineNode.lineWidth = 2;
[self addChild:lineNode];
}
- (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event
{
UITouch* touch = [touches anyObject];
CGPoint positionInScene = [touch locationInNode:self];
CGPathAddLineToPoint(pathToDraw, NULL, positionInScene.x, positionInScene.y);
lineNode.path = pathToDraw;
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
// lineNode.physicsBody = [SKPhysicsBody bodyWithPolygonFromPath:pathToDraw];
// lineNode.physicsBody.dynamic = NO;
// lineNode.physicsBody.categoryBitMask = lines;
// lineNode.physicsBody.contactTestBitMask = 0;
// lineNode.physicsBody.collisionBitMask = blueParticles | redParticles| yellowParticles;
CGPathRelease(pathToDraw);
}
Edit------ Code works when background node removed..How can I set up my background node so that I can daw overtop? Thanks
SKSpriteNode *background;
if(screenHeight == 480){
background = [SKSpriteNode spriteNodeWithImageNamed:#"iPhone4BG.png"];
}
if(screenHeight == 568){
background = [SKSpriteNode spriteNodeWithImageNamed:#"iPhone5BG.png"];
}
if(screenHeight == 667){
background = [SKSpriteNode spriteNodeWithImageNamed:#"iPhone6BG.png"];
}
if(screenHeight == 736){
background = [SKSpriteNode spriteNodeWithImageNamed:#"iPhone6PlusBG.png"];
}
background.position = CGPointMake(screenWidth/2, screenHeight/2);
background.size = CGSizeMake(screenWidth, screenHeight);
// [self addChild:background];
NOTE Core issue was Simulator not rendering a path that isn't closed. On the device the issue doesn't exist. Also a background element was involved that the original poster didn't mention and wasn't represented in code. This answer will solve the issue on the simulator.
To fix your problem, modify your code as follows :
- (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event
{
UITouch* touch = [touches anyObject];
CGPoint positionInScene = [touch locationInNode:self];
CGPathAddLineToPoint(pathToDraw, NULL, positionInScene.x, positionInScene.y);
// add this line to fix your issue
CGPathMoveToPoint(pathToDraw, NULL, positionInScene.x, positionInScene.y);
lineNode.path = pathToDraw;
}
The core issue is that when it goes to render the path, it doesn't have a complete path. Your current approach is to be constantly modifying the path on each touchMove.
The way you close a sub path is :
CGPathCloseSubpath(pathToDraw);
However I chose to just use CGPathMoveToPoint each time you added a segment to the path.
From CGPath reference for CGPathMoveToPoint :
This function ends the subpath already in progress (if any) and starts a new subpath, initializing the starting point and the current point to the specified location (x,y) after an optional transformation.
I am closing the current subpath before it gets rendered, and also setting the start location for the next segment.
However, you are going to run into major issues with performance as defined in the link in the the comments if you are going to be drawing a long line or many lines. Draw a really long line, and you will see what I mean. With minimal line drawing, you might get away with the current approach.
Sadly, drawing of this nature with SKShapeNode is not as optimized as it should be.
I think this is what you are trying to implement. It draws a temporary line (white) as you move your finger and draws a final line (red) when you lift your finger. You will need to declare a CGPoint instance variable named startingPoint and delete the CGMutablePathRef ivar named pathToDraw. The code can be modified to draw multiple, connected line segments as well.
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch* touch = [touches anyObject];
CGPoint positionInScene = [touch locationInNode:self];
startingPoint = positionInScene;
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch* touch = [touches anyObject];
CGPoint positionInScene = [touch locationInNode:self];
// Remove temporary line if it exist
[lineNode removeFromParent];
CGMutablePathRef pathToDraw = CGPathCreateMutable();
CGPathMoveToPoint(pathToDraw, NULL, startingPoint.x, startingPoint.y);
CGPathAddLineToPoint(pathToDraw, NULL, positionInScene.x, positionInScene.y);
lineNode = [SKShapeNode node];
lineNode.path = pathToDraw;
//CGPathRelease(pathToDraw);
lineNode.strokeColor = [SKColor whiteColor];
lineNode.lineWidth = 1;
[self addChild:lineNode];
}
- (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event
{
UITouch* touch = [touches anyObject];
CGPoint positionInScene = [touch locationInNode:self];
// Remove temporary line
[lineNode removeFromParent];
CGMutablePathRef pathToDraw = CGPathCreateMutable();
CGPathMoveToPoint(pathToDraw, NULL, startingPoint.x, startingPoint.y);
CGPathAddLineToPoint(pathToDraw, NULL, positionInScene.x, positionInScene.y);
SKShapeNode *finalLineNode = [SKShapeNode node];
finalLineNode.path = pathToDraw;
//CGPathRelease(pathToDraw);
finalLineNode.strokeColor = [SKColor redColor];
finalLineNode.lineWidth = 1;
[self addChild:finalLineNode];
}
I am trying to make an animated character that can reach out for things on the screen, without moving his torso.
If he can not reach than his hand should go the maximal amount in this direction and then be counteracted by the physical limitation of his static torso.
So in my code the green rectangle (hand) moves where I click and the torso follows.
I would like to be able to make the red rect (body) stationary and make the green rectangle "point" at the touch point position.
I have tried to apply a fixed joint to between the scene and the red body rectangle, but this did not seem to work.
- (void) createSceneContent
{
SKSpriteNode *body = [SKSpriteNode spriteNodeWithColor:[SKColor redColor] size:CGSizeMake(100, 100)];
body.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));
[self addChild:body];
body.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:body.size];
body.physicsBody.affectedByGravity = NO;
body.physicsBody.dynamic = YES;
self.hand = [SKSpriteNode spriteNodeWithColor:[SKColor greenColor] size:CGSizeMake(150, 20)];
self.hand.position = CGPointMake(body.position.x + body.size.width / 2 + self.hand.size.width / 2, body.position.y);
[self addChild:self.hand];
self.hand.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:self.hand.size];
self.hand.physicsBody.dynamic = NO;
self.scene.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:self.frame];
[self.physicsWorld addJoint:[SKPhysicsJointPin jointWithBodyA:body.physicsBody bodyB:self.hand.physicsBody anchor:CGPointMake(body.position.x + body.size.width / 2, body.position.y)]];
[self.hand runAction:[SKAction moveByX:100 y:10 duration:0.1]];
}
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInNode:self];
[self.hand runAction:[SKAction moveTo:location duration:0.1]];
}
First of all, you don't really need to use physics here, if you only want the arm to point at the touched location.
Just change the arm node's anchorPoint to the shoulder (where you would put the pin on the joint) and rotate it around that point (the point is held in a helper shoulderNode, so that it's easy to convert to its coordinates later). You can calculate the rotation angle using atan2f. Here's the code:
- (void) createSceneContent
{
SKSpriteNode *body = [SKSpriteNode spriteNodeWithColor:[SKColor redColor] size:CGSizeMake(100, 100)];
body.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));
[self addChild:body];
self.hand = [SKSpriteNode spriteNodeWithColor:[SKColor greenColor] size:CGSizeMake(150, 20)];
self.hand.position = CGPointMake(body.position.x + body.size.width*0.5, body.position.y);
self.hand.anchorPoint = CGPointMake(0, 0.5);
[self addChild:self.hand];
SKNode *shoulderNode = [SKNode node];
shoulderNode.position = self.hand.position;
shoulderNode.name = #"shoulderNode";
[self addChild:shoulderNode];
}
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInNode:self];
CGPoint locationConv = [self convertPoint:location toNode:[self childNodeWithName:#"shoulderNode"]];
self.hand.zRotation = (atan2f(locationConv.y, locationConv.x));
}
On the other hand, if you ever need to use physics bodies here, it's probably a bad idea to use SKActions to move the arm, and you also might want to set body.physicsBody.dynamic = NO to make the torso static. Then, make sure you add the pin joint correctly, and set its frictionTorque property to a high value to get less dangling. One of the ways to make the hand point at the right location is its velocity vector set in the update method - just use the converted location coordinates (here they are relative to the body node's centre). Full example code:
- (void) createSceneContent {
self.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:self.frame];
body = [SKSpriteNode spriteNodeWithColor:[SKColor redColor] size:CGSizeMake(100, 100)];
body.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));
[self addChild:body];
body.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:body.size];
body.physicsBody.dynamic = NO;
body.physicsBody.affectedByGravity = YES;
self.hand = [SKSpriteNode spriteNodeWithColor:[SKColor greenColor] size:CGSizeMake(20, 150)];
// experiment with the anchorPoint for different results
self.hand.anchorPoint = CGPointMake(0.5, 0);
self.hand.position = CGPointMake(body.position.x, body.position.y);
[self addChild:self.hand];
self.hand.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:self.hand.size];
self.hand.physicsBody.dynamic = YES;
self.hand.physicsBody.affectedByGravity = NO;
SKPhysicsJointPin *pinHand = [SKPhysicsJointPin jointWithBodyA:body.physicsBody bodyB:self.hand.physicsBody anchor:CGPointMake(body.position.x, body.position.y-5)];
[self.physicsWorld addJoint:pinHand];
// experiment with the friction torque value to achieve desired results
pinHand.frictionTorque = 1.0;
}
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
location = [touch locationInNode:self];
}
-(void)update:(CFTimeInterval)currentTime {
CGPoint locationConv = [self convertPoint:location toNode:body];
// experiment with the multiplier (here: 10) for faster/slower movement
self.hand.physicsBody.velocity = CGVectorMake(locationConv.x*10, locationConv.y*10);
}
Hope that helps!