Creating a trail with SKEmitterNode and particles in SpriteKit - sprite-kit

I am trying to make it so a particle I made follows the player whenever the player is moved. The effect I am trying to replicate is like when you are on a website and they have some sort of set of objects following your mouse. I tried to do this by making the particle move by the amount the player does, but it is not reproducing the intended effect. Any suggestions? My code:
Declaring particle
NSString *myParticlePath = [[NSBundle mainBundle] pathForResource:#"trail" ofType:#"sks"];
self.trailParticle = [NSKeyedUnarchiver unarchiveObjectWithFile:myParticlePath];
self.trailParticle.position = CGPointMake(0,0);
[self.player addChild:self.trailParticle];
Move method
-(void)dragPlayer: (UIPanGestureRecognizer *)gesture {
if (gesture.state == UIGestureRecognizerStateChanged) {
//Get the (x,y) translation coordinate
CGPoint translation = [gesture translationInView:self.view];
//Move by -y because moving positive is right and down, we want right and up
//so that we can match the user's drag location (SKView rectangle y is opp UIView)
CGPoint newLocation = CGPointMake(self.player.position.x + translation.x, self.player.position.y - translation.y);
CGPoint newLocPart = CGPointMake(self.trailParticle.position.x + translation.x, self.trailParticle.position.y - translation.y);
//Check if location is in bounds of screen
self.player.position = [self checkBounds:newLocation];
self.trailParticle.position = [self checkBounds:newLocPart];
self.trailParticle.particleAction = [SKAction moveByX:translation.x y:-translation.y duration:0];
//Reset the translation point to the origin so that translation does not accumulate
[gesture setTranslation:CGPointZero inView:self.view];
}
}

Try this:
1) If your emitter is in the Scene, use this emitter's property targetNode and set is as Scene. That means that particles will not be a child of emitter but your scene which should leave a trail..
Not sure if this is correct (I do it in C#):
self.trailParticle.targetNode = self; // self as Scene
And some extra:
2) I think you could rather attach your emitter to self.player as child so it would move together and automatically with your player and then there is no need of this:
self.trailParticle.position = [self checkBounds:newLocPart];
self.trailParticle.particleAction = [SKAction moveByX:translation.x y:-translation.y duration:0];

In your update loop Check if your player is moving along the X axis.
Then create a sprite node each time this is happening. In this example name your player "player1"
The key here is your particle must have a max set in the particles column near birthrate.
The blow code works for me.
-(void)update:(CFTimeInterval)currentTime {
// Find your player
SKNode* Mynode = (SKSpriteNode *)[self childNodeWithName:#"player1"];
// Check if our player is moving. Lower the number if you are not getting a trail.
if (Mynode.physicsBody.velocity.dx>10|
Mynode.physicsBody.velocity.dy>10|
Mynode.physicsBody.velocity.dx<-10|
Mynode.physicsBody.velocity.dy<-10){
// Unpack your particle
NSString *myParticlePath = [[NSBundle mainBundle] pathForResource:#"trail" ofType:#"sks"];
//Create your emitter
SKEmitterNode *myTrail = [NSKeyedUnarchiver unarchiveObjectWithFile:myParticlePath];
// This ensures your trail doesn't stay forever . Adjust this number for different effects
myTrail.numParticlesToEmit=10;
// The length of your trail - higher number, longer trail.
myTrail.particleLifetime = 2.0;
//Set the trail position to the player position
myTrail.position=Mynode.position;
//Add the trail to the scene
[self addChild:myTrail];
}
}

Related

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 :)

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.

Collision of Sprites in CCSpriteBatchNode and CCParallaxNode

I have two sprites one is added as the child of CCSpriteBatchNode and the other as the child of CCParallaxNode. Is there any method to detect their collision? I have used the following code.
-(void)CheckCollition:(CCSprite *)Opp_Obs Opponent:(CCSprite *) H_man
{
// NSLog(#"inside check collision");
CGRect b_rect=[Opp_Obs boundingBox];
CGPoint p_position=[H_man position];
if (CGRectContainsPoint(b_rect,p_position))
{
NSLog(#"collision with opponent");
// Zoom Animation with Points
CCScaleBy *zzomscal=[CCScaleTo actionWithDuration:.2 scale:.12];
CCRotateTo * rotLeft = [CCRotateBy actionWithDuration:0.2 angle:360];
CCCallFunc *ccfun=[CCCallFunc actionWithTarget:self selector:#selector(zoomComplete)];
CCSequence * zzomseq = [CCSequence actions:zzomscal,rotLeft,ccfun, nil];
[H_man runAction:zzomseq];
}
else
{
NSLog(#"no collision");
}
}
But here the control never enters into the loop. Is there any other solution? Anyone please help me.
Set a breakpoint and compare the values of rect and position. One of them may be zero, or way off.
In the latter case you may need to convert the bbox origin and position to world coordinates first in order to compare them. This is the case when the sprites' parents are moving too (parent position != 0,0).

Cocos2d - how to make individual particles follow the layer, not the emitter?

I have a CCSprite and a CCParticleSystemQuad that are both children of the CCLayer. In my update method, I set the emitter's position to that of the sprite, so it tracks the sprite around. The smoke puff fall out the bottom of the sprite like you'd expect and even though you move the sprite around, the smoke appears to be part of the background layer.
The problem come if I match up their rotations. Now, for example if my sprite is rocking back and forth, the puffs of smoke swing in an arc and appear attached to the sprite.
How can I make the puffs of smoke continue along the parent layer in a straight line and not rotate with the sprite? They don't translate with the sprite when I move it, so why do they rotate with it?
EDIT: adding code...
- (id)init
{
if (!(self = [super init])) return nil;
self.isTouchEnabled = YES;
CGSize screenSize = [[CCDirector sharedDirector] winSize];
sprite = [CCSprite spriteWithFile:#"Icon.png"]; // declared in the header
[sprite setPosition:ccp(screenSize.width/2, screenSize.height/2)];
[self addChild:sprite];
id repeatAction = [CCRepeatForever actionWithAction:
[CCSequence actions:
[CCRotateTo actionWithDuration:0.3f angle:-45.0f],
[CCRotateTo actionWithDuration:0.6f angle:45.0f],
[CCRotateTo actionWithDuration:0.3f angle:0.0f],
nil]];
[sprite runAction:repeatAction];
emitter = [[CCParticleSystemQuad particleWithFile:#"jetpack_smoke.plist"] retain]; // declared in the header - the particle was made in Particle Designer
[emitter setPosition:sprite.position];
[emitter setPositionType:kCCPositionTypeFree]; // ...Free and ...Relative seem to behave the same.
[emitter stopSystem];
[self addChild:emitter];
[self scheduleUpdate];
return self;
}
- (void)update:(ccTime)dt
{
[emitter setPosition:ccp(sprite.position.x, sprite.position.y-sprite.contentSize.height/2)];
[emitter setRotation:[sprite rotation]]; // if you comment this out, it works as expected.
}
// there are touches methods to just move the sprite to where the touch is, and to start the emitter when touches began and to stop it when touches end.
I found the answer on a different site - www.raywenderlich.com
I don't know why this is true, but it seems that CCParticleSystems don't like to be rotated while you move them around. They don't mind changing their angle property. Actually, there may be cases where you want that behavior.
Anyway I made a method that adjusts the emitter's angle property and it works fine. It takes your touch location and scales the y component to be the angle.
- (void)updateAngle:(CGPoint)location
{
float width = [[CCDirector sharedDirector] winSize].width;
float angle = location.x / width * 360.0f;
CCLOG(#"angle = %1.1f", angle);
[smoke_emitter setAngle:angle]; // I added both smoke and fire!
[fire_emitter setAngle:angle];
// [emitter setRotation:angle]; // this doesn't work
}
CCSprite's anchorPoint is {0.5f, 0.5f), while the emitter descends directly from CCNode, which has an anchorPoint of {0.0f, 0.0f}. Try setting the emitter's anchorPoint to match the CCSprite's.

bounding a sprite in cocos2d

am making a canon to fire objects. back of the canon the plunger is attached. plunger acts for set speed and angle. canon rotates 0-90 degree and plunger moves front and back for adjust speed. when am rotates the canon by touches moved its working fine. when plunger is pull back by touches moved and it rotates means the plunger is bounds outside of the canon.
how to control this:-
my code for plunger and canon rotation on touches moved. ( para3 is the canon , para6 is my plunger):-
CGPoint touchLocation = [self convertTouchToNodeSpace:touch];
CGPoint oldTouchLocation = [touch previousLocationInView:touch.view];
oldTouchLocation = [[CCDirector sharedDirector] convertToGL:oldTouchLocation];
oldTouchLocation = [self convertToNodeSpace:oldTouchLocation];
if (CGRectContainsPoint(CGRectMake(para6.position.x-para6.contentSize.width/2, para6.position.y-para6.contentSize.height/2, para6.contentSize.width, para6.contentSize.height), touchLocation) && (touchLocation.y-oldTouchLocation.y == 0))
{
CGPoint diff = ccpSub(touchLocation, oldTouchLocation);
CGPoint currentpos = [para6 position];
NSLog(#"%d",currentpos);
CGPoint destination = ccpAdd(currentpos, diff);
if (destination.x < 90 && destination.x >70)
{
[para6 setPosition:destination];
speed = (70 + (90-destination.x))*3.5 ;
}
}
if(CGRectIntersectsRect((CGRectMake(para6.position.x-para6.contentSize.width/8, (para6.position.y+30)-para6.contentSize.height/10, para6.contentSize.width, para6.contentSize.height/10)),(CGRectMake(para3.position.x-para3.contentSize.width/2, para3.position.y-para3.contentSize.height/2, para3.contentSize.width, para3.contentSize.height))))
{
[para3 runAction:[CCSequence actions:
[CCRotateTo actionWithDuration:rotateDuration angle:rotateDiff],
nil]];
CGFloat plungrot = (rotateDiff);
CCRotateTo *rot = [CCRotateTo actionWithDuration:rotateDuration angle:plungrot];
[para6 runAction:rot];
}
}
how about u do this that you use the [CCMoveTo actionWithDuration: position:] method??
Through this method you can easily control the speed by the "actionWithDuration" argument which takes integer values of time in seconds, while direction can be adjusted through "position" argument which takes ccp(x,y) as the values to the point you want your plunger to move to.
You can use it like this....
CCSprite *plunger = [[CCSprite alloc] initWithFile:#"plunger.png"];
plunger.position = ccp(240,240);
[self addChild:plunger z:10];
[plunger release];
id = [CCMoveTo actionWithDuration:3 position:ccp(300,240)];
The values given are of my choice. You may use them to your accordance.
Hope it helps you....
I hope i understood the question correctly:
if the problem is, that the cannon and plunger both rotate around their own center points, but you want them to rotate together, then the solution should be to make the plunger a child sprite of the cannon (this also makes the plugers position relative to the cannon) i.e.
[para3 addChild:para6]
then you only need to rotate the cannon and the plunger will be rotatet with it.
if i got your question totally wrong, maybe you could post a screenshot :-)