How do you stop a particle effect? (SKEmitterNode) - sprite-kit

I currently have this code in a collide statement where if collide with object then this particle happens but how do I stop it? As it goes on forever whereas I only want to happen a couple of times per contactetc
SKEmitterNode *emitter = [NSKeyedUnarchiver unarchiveObjectWithFile:[[NSBundle mainBundle] pathForResource:#"ff" ofType:#"sks"]];
emitter.zPosition = 0;
emitter.particlePositionRange = CGVectorMake(0, self.size.height);
emitter.position = CGPointMake(self.size.width, self.size.height/2);
[self addChild:emitter];

When you use the particle-editor you can set the maximum number of particles to create. It's the field below "Particle Texture".The official description is:
"The maximum number of particles that the emitter creates over the emitter’s lifetime. After this number is reached, no more particles are created by the emitter. Enter 0 to remove particle limits."
Also see: Particle Emitter Editor Guide
Of course, you should remove the emitter-node from its parent after it created the maximum number of particles. This can be done by creating an action-sequence that waits for a few seconds and removes the emitter-node from its parent [SKAction removeFromParent].

Use setParticleBirthRate:0 to stop emitting particles. This is the most realistic way to turn off an emitter.
If you want it to disappear immediately then use removeFromParent.
If using setParticleBirthRate remember the original value to turn it back on later. for instance.
#implementation GameScene
{
SKEmitterNode *rocketfire;
float rocketfire_birthrate;
}
// ...
-(void)init {
// ...
rocketfire_birthrate = rocketfire.particleBirthRate;
}
// turn on the emitter (thrust rocket) when the screen is touched
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[rocketfire setParticleBirthRate:rocketfire_birthrate];
}
// turn off the emitter on touches ended
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
[rocketfire setParticleBirthRate:0];
}

This is similar to the answer here provided by Tapir, with the exception that I don't use the selector. I'm not sure if that is required in previous versions but in ios 10 I don't see my node counts increasing and it does work. ie. add an emitter, pop a specified number of particles and then remove the emitter. In this case, I've modified it to add a single particle.
-(void) gainPoints:(int)x y:(int)y {
NSString *pPlus1Path = [[NSBundle mainBundle] pathForResource:#"PlusEmitter" ofType:#"sks"];
SKEmitterNode *pEmitter = [NSKeyedUnarchiver unarchiveObjectWithFile:pPlus1Path];
// Place the emitter at the specified position
pEmitter.position = CGPointMake(x,y);
pEmitter.name = #"plus1";
pEmitter.particleLifetime = 1;
pEmitter.particleBirthRate = 1;
pEmitter.numParticlesToEmit = 1;
// Send the particles to the scene.
pEmitter.targetNode = self.scene;
[self addChild:pEmitter];
SKAction *pRemoveNode = [SKAction removeFromParent];
SKAction *pWaitForSecsN = [SKAction waitForDuration:1];
SKAction *pSequence = [SKAction sequence:#[pWaitForSecsN,pRemoveNode]];
[pEmitter runAction:pSequence];
}

SWIFT 4:
I think I have a slightly better approach for turning it off:
1) Put the birth rate to 0:
pEmitter.particleBirthRate = 0
2) Run a delay action for the lifetime:
let waitAction = SKAction.wait(forDuration: pEmitter.particleLifetime)
pEmitter.run(waitAction, completion: {
pEmitter.removeFromParent()
})
NOTE: Might not be 100% accurate if you have a range of partilcelifetime.

For those who want to freeze a particle emitter on a specific part of the animation, instead of removing it from screen, you can pause the emitter:
emitter.isPaused = true

when pause you need :
emitter.hidden = true
when restart you need :
emitter.hidden = false
emitter.resetSimulation()

Related

SKSpriteNode takes too much time to be created from texture

I have a field full of sprites (30x30).
Before creating any sprite I am preloading my texture atlas with this code:
- (void)preloadAllAtlases
{
BGLog();
_tilesAtlas = [SKTextureAtlas atlasNamed:#"Tiles"];
_tilesTextures = [NSMutableDictionary new];
__weak typeof (shared) weakSelf = self;
[SKTextureAtlas preloadTextureAtlases:#[_tilesAtlas]
withCompletionHandler:^
{
for (NSString *textureFullName in weakSelf.tilesAtlas.textureNames) {
NSString *textureName = [textureFullName componentsSeparatedByString:#"#"][0];
weakSelf.tilesTextures[textureName] = [SKTexture textureWithImageNamed:textureName];
}
}];
}
This method is called from singleton once - in appicationDidFinishLaunchingWithOptions.
When time comes I generate SKSpriteNodes from SKTextures and add compound node (node which contains all SKSpriteNodes) to SKScene. But... it takes from 1 to 1.5 seconds to display/render 192 sprites. With "Time profiler" I have found that [SKSpriteNode spriteNodeWithTexture:] takes waaaaay to much time.
Screenshot:
Is there any way to speed up sprite creation?
Thx.

SpriteKit - Trying to implement a a 2.d.p timer which increments per 1 second. How can I do this?

I've tried using NSTimer but cannot get my head round it.
Any help would be much appreciated
If you're wanting to do something game logic related, it's pretty easy to rig the update method to do what you want.
-(void)update:(CFTimeInterval)currentTime
{
/* Called before each frame is rendered */
// Handle time delta.
// If we drop below 60fps, we still want things to happen at the same realtime rate.
CFTimeInterval timeSinceLast = currentTime - lastUpdateTimeInterval;
lastUpdateTimeInterval = currentTime;
[self updateWithTimeSinceLastUpdate:timeSinceLast];
}
- (void)updateWithTimeSinceLastUpdate:(CFTimeInterval)timeSinceLast
{
lastEventInterval += timeSinceLast;
//this is the part that will function like a timer selector and run once a second.
if (lastEventInterval > 1)
{
lastEventInterval = 0;
//do whatever you want to do once a second here
}
}
You can do this with SKActions. For example...
SKAction *wait = [SKAction waitForDuration:1.0f];
SKAction *sequence = [SKAction sequence:#[[SKAction performSelector:#selector(methodToFire:) onTarget:self], wait]];
SKAction *repeat = [SKAction repeatActionForever:sequence];
[self runAction:repeat];
- (void)methodToFire {
...
}

SpriteKit Touches - containsPoint

I'm having an issue where, within my touchesBegan method, I'm not getting back what I think I should.
I'm testing for a hit within a specific node. I've tried several methods, and none work. I've created a work-around, but would love to know if this is a bug or if I'm doing something wrong.
Here's the code:
Standard touchesBegan method:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
/* Called when a touch begins */
SKSpriteNode *infoPanelNode = (SKSpriteNode *)[self childNodeWithName:#"infoPanelNode"];
UITouch *touch = [touches anyObject];
if(touch){
//e.g. infoPanelNode position:{508, 23} size:{446, 265.5} (also of note - infoPanelNode is a child of self)
//This solution works
CGPoint location = [touch locationInNode:self];
//location == (x=77, y=170)
bool withinInfoPanelNode = [self myPtInNode:infoPanelNode inPoint:location];
// withinInfoPanelNode == false (CORRECT)
//This one doesn't return the same result - returns true when hit is not in the cell
CGPoint infoLocation = [touch locationInNode:infoPanelNode];
//infoLocation == (x=-862, y=294)
bool withinInfoPanelNodeBuiltInResult = [infoPanelNode containsPoint:infoLocation];
// withinInfoPanelNodeBuiltInResult == true (WRONG)
// This one doesn't work either - returns an array with the infoPanelNode in it, even though the hit point and node location are the same shown above
// NSArray *nodes = [self nodesAtPoint:location];
// for (SKNode *node in nodes) {
// if(node==infoPanelNode)
// withinInfoPanelNode = true;
// }
//
//Code omitted - doing something with the withinInfoPanelNode now
}
My custom hit test code:
-(bool) myPtInNode:(SKSpriteNode *)node inPoint:(CGPoint)inPoint {
if(node.position.x < inPoint.x && (node.position.x+node.size.width) > inPoint.x){
if(node.position.y < inPoint.y && (node.position.y+node.size.height) > inPoint.y){
return true;
}
}
return false;
}
Anyone see what's going wrong here?
Thanks,
kg
I'm not sure exactly how SKCropNode will work, but in general, in order for containsPoint: to detect touches within it you need to give it a point relative to its parent node. The following code should work. Note the addition of .parent when calling locationInNode:
CGPoint infoLocation = [touch locationInNode:infoPanelNode.parent];
BOOL withinInfoPanelNodeBuiltInResult = [infoPanelNode containsPoint:infoLocation];
Solved this problem and wanted to update everyone.
It turns out that with this specific infoPanelNode (an SKSpriteNode), I have a child node within it that is an SKCropNode. This node then crops out a much larger node (obviously it's a child of the crop node) so only a small portion is viewable (allowing for scrolling to portions of that node). Unfortunately, the call to containsPoint apparently combines the boundaries of all child nodes with the receiving node's boundaries to use as the boundary test rect. This would be understandable if it would respect the SKCropNode's boundaries of IT'S children, but apparently, it doesn't so you have to roll your own if you have this type of setup.

Removing animation sprite frames from layer?

Let's say I have a character in a game and its class is like this.
#interface Player
{
CCSprite* stand;
CCAnimation* run;
}
-(void) playRunAction
{
// Create CCAnimate* object from CCAnimation object (run)
[self runAction:runAniate];
}
-(void) playStandAction
{
stand.visible = YES;
[self stopAllActions];
}
The player has ability to stand or run.
But one problem is, after playStandAction is called, stand animation is visible and running animation stopped, but one frame of running animation still there!
( Now you see 'stand sprite' AND 'one of running animation frame' together. )
How can I make running animation not visible?
P.s Can anyone throw me a better way of managing animation in one character? This is totally disaster as animations added.
-(void) playStandAction
{
//Make the animation object.visible = NO; here
stand.visible = YES;
[self stopAllActions];
}
and in
-(void) playRunAction
{
// Create CCAnimate* object from CCAnimation object (run)
//Make the animation object.visible = YES; here
stand.visible = NO;
[self runAction:runAniate];
}
Use method with parameter restoreOriginalFrame and pass it yes
I don't know which method you are calling for creating CCAnimate object...
Like this:
[CCAnimate actionWithAnimation:animation restoreOriginalFrame:YES]];
And don't call runAction on layer. I would prefer you to runAction on sprite itself...
You don't need to hide and show 2 different objects...
Hope this helps. :)

Remove sprite from screen cocos2d iphone?

I have a game that I wrote. I am about ready to call it finished but I found a bug. Basically the game gets slower as the longer you play. My guess is this is due to sprites that are still being drawn off screen. I will paste the code below but basically the sprite is created in the "addNewBall" method. In this method it is added to an array which calculates its motion. After the ball reaches a position where it is off the screen it is removed from the array which causes it to stop moving but it is still being "drawn" off screen. How do I remove the sprite so the processor no longer calculates it. Thanks in advance for your help!
Tanner
Code:
-(void) addNewBall {
NumberOfBalls = NumberOfBalls + 1;
int RandomXPosition = (arc4random() % 240) + 40;
NSString *BallFileString = #"OrangeBall.png";
switch (arc4random() % 5) {
case 1:
BallFileString = #"OrangeBall.png";
break;
case 2:
BallFileString = #"GreenBall.png";
break;
case 3:
BallFileString = #"YellowBall.png";
break;
case 4:
BallFileString = #"PinkBall.png";
break;
case 0:
BallFileString = #"BlueBall.png";
break;
}
Ball = [CCSprite spriteWithFile:BallFileString];
Ball.position = ccp(RandomXPosition, 520);
BallIsMoving = YES;
[self addChild:Ball z:10];
[AllObjectsArray_ addObject:Ball];
[BallArray_ addObject:Ball];
}
//And here is where it is removed...
if (Ball.position.y <= -100) {
[BallArray_ removeObject: Ball];
}
You seem to be missing some conditions in your removal method. Don't you also want to remove the ball if its y position is greater than the screen height, or if its x position is off-screen? At any rate, in the same place that you're removing the ball from the array, you should add:
[self removeChild:Ball cleanup: YES]
I should also point out that your BallArray is probably redundant, since you're adding all the balls to another node anyway. If the only children of that node are Balls, you can get the array of balls using its children property. In this case, the child array would be: self.children (See http://www.cocos2d-iphone.org/api-ref/latest-stable/interface_c_c_node.html#a5e739ecda0c314283a89ac389dfca2fa for more info.)
If you have non-Ball children on the same node, you might want to add an intermediate node to simplify the design so that you can use one less array.
You said you are removing the objects from the arrays, but you didn't mention that you are also removing the sprite from the parent CCNode.
Check the methods from CCNode to remove childs: http://www.cocos2d-iphone.org/api-ref/latest-stable/interface_c_c_node.html#a0d4e615f688458c74001acf10f0ae011
You could use:
[Ball removeFromParentAndCleanup:YES];
This will remove the ball from it's parent CCNode and will remove all actions and callbacks.
You need to specify your sprite,and you can use this following line..
[self removeChild:Ball cleanup: YES]