SKSpriteNode takes too much time to be created from texture - sprite-kit

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.

Related

How do you stop a particle effect? (SKEmitterNode)

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

Keeping track of sprites

I'm creating a Cocos2D app that will have between 4 - 12 sprites that the user can interact with/move (Sprites 1 - 4 in the pic). There will be an equal number of target sprites that the user will be able to drag the other sprites to (Targets 1 - 4 in the pic). I was thinking about adding tags to all of the sprites to differentiate between them. What I'm having difficulty with is determining the proper way to keep track of the moveable sprites in relation to what target sprites each of them are on top of.
How should I keep track of these relationships?
Sprite 1 - Target 3
Sprite 2 - Target 1
Sprite 3 - Target 2
Sprite 4 - Target 4
Create two different isntance mutable arrays to hold your objects and an array to hold your correct answers i.e.
//In your .h
NSMutableArray *_targets;
NSMutableArray *_moveableSprites;
NSArray *_answers;
//In your .m
_targets = [[NSMutableArray alloc] init];
_moveableSprites = [[NSMutableArray alloc] init];
_answers = [[NSArray alloc] initWithObjects:#"101", #"103", #"102", #"104", nil];
When you create your sprites assign a tag value to them i.e.
CCSprite *target1 = [CCSprite spriteWithFile:#"file.png"];
//Make sure target tags start from 0 and increment up
target1.tag = 0;
Then before you add each sprite to your layer add them into your respective arrays i.e.
[_targets addObject:target1];
You can then write a method to check sprite position (something similar to this):
- (void)checkSprites {
for(CCSprite *sprite in _moveableSprites) {
for(CCSprite *target in _targets) {
if(CGRectIntersectsRect(sprite.boundingBox, target.boundingBox)) {
if(sprite.tag == [_answers objectAtIndex:target.tag]) {
// Correct sprite in target.....do stuff
} else {
// Not correct sprite.....do stuff
}
}
}
}
}

Slow preformance during replaceScene

I'm facing a performance issue whenever the code reach "replaceScene". It happens only in the Play Scene. So after the game is over, I display a score, and then, it's time for CCDirector to do replaceScene in order to go back to the main menu.
After waiting for around 20 seconds then it finally display the main menu. But somehow this is not right, player will feel that the game suddenly hang. Then I tried to put some animation like preloader, it happens the same, the preloader picture did animate a while then suddenly stop, and I think due to the same issue triggered by replaceScene, although still it'll display the main menu scene. Care to give some tips how to speed up the releasing of all the objects which no longer needed.
Hoping to get a solution from experts here. Thanks.
Here is my code :
............
//button at the score pop up sprite
CCMenuItem *btContinue = [CCMenuItemImage itemFromNormalImage:BTCONTINUE
selectedImage:BTCONTINUE_ON
target:self
selector:#selector(goLoader)];
btContinue.anchorPoint = ccp(0,0);
btContinue.position = ccp(340, 40);
CCMenu *menu = [CCMenu menuWithItems:btContinue, nil];
menu.position = CGPointZero;
[self addChild:menu z:ZPOPUP_CONTENT];
//prepare the loader, but set visible to NO first
CCSprite *loaderBg = [CCSprite spriteWithFile:LOADER_FINISH];
loaderBg.anchorPoint = ccp(0,0);
loaderBg.position = ccp(0,0);
loaderBg.visible = NO;
[self addChild:loaderBg z:ZLOADER_BG tag:TAG_LOADER_BG];
NSLog(#"prepare loader finish");
//animate loader
CCSprite *loaderPic = [[CCSprite alloc] initWithSpriteFrame:[[CCSpriteFrameCache sharedSpriteFrameCache]
spriteFrameByName:LOADER]];
loaderPic.anchorPoint = ccp(0.5,0.5);
loaderPic.position = ccp(200,35);
loaderPic.visible = NO;
[self addChild:loaderPic z:ZLOADER_PIC tag:TAG_LOADER_PIC];
[loaderPic runAction:[CCRepeatForever actionWithAction:[CCRotateBy actionWithDuration:0.05f angle:10.0f]]];
}
-(void)goLoader {
NSLog(#"goMainMenuScene");
CCSprite *tmpBg = (CCSprite *) [self getChildByTag:TAG_LOADER_BG];
if (tmpBg != nil)
tmpBg.visible = YES;
CCSprite *tmpPic = (CCSprite *) [self getChildByTag:TAG_LOADER_PIC];
if (tmpPic != nil)
tmpPic.visible = YES;
double time = 2.0;
id delay = [CCDelayTime actionWithDuration: time];
id proceed = [CCCallFunc actionWithTarget:self selector:#selector(goMainMenuScene)];
id seq = [CCSequence actions: delay, proceed, nil];
[self runAction:seq];
}
-(void)goMainMenuScene {
[[GameManager sharedGameManager] runSceneWithID:SCENE_MAIN_MENU];
}
Your problem is most likely the new scene, and whatever happens in the new scene's and its child node's init methods. Loading resource files can take quite a while. If you defer doing this into onEnter you might see better results. But 20 seconds, that's a lot. Check what you're doing that takes THIS long. I bet it's loading a gross amount of resources, or loading them in an extremely inefficient way. JPG files are known to load very slowly, if you use JPG convert them to PNG.

Cocos2d. One instance of CCLayer with particles for a lot of scenes?

I want to create a starry sky for some scenes.
The main problem is it needs some time to fill all the screen with particles. Someone advices me to create the whole sky at the beginning and save it between its calls.
I tried something like this:
#implementation StarrySky
static StarrySky *_starrySky;
- (id)init
{
if ((self = [super init])) {
NSArray *starsArray = [NSArray arrayWithObjects:#"Stars1.plist", #"Stars2.plist", #"Stars3.plist", nil];
for(NSString *stars in starsArray) {
CCParticleSystemQuad *starsEffect = [CCParticleSystemQuad particleWithFile:stars];
[self addChild:starsEffect z:-2];
}
}
return self;
}
+ (StarrySky *)sharedStarrySky
{
if (!_starrySky) {
_starrySky = [[StarrySky alloc] init];
}
return _starrySky;
}
- (void)dealloc
{
_starrySky = nil;
[super dealloc];
}
#end
But the particles stop moving.
One solution that ought to work is to create the star particle system and add it to your first scene (ie main menu). Set it to invisible or move it way off the screen or behind the background image (lowest z-order) so it can create some stars without actually showing any stars in the menu.
Now when you switch to your actual game scene, you alloc/init the game scene and then remove the star particle system from the currently running scene and add it to the game scene. Basically you're moving it from one scene to another.
Now you can call replaceScene with the game scene which now contains the already active star particle system.

cocos2d ccArray removing object is slow

I'm making a particle system for my game, which basically is smoke coming out from rockets in my game. All particles are in a ccArray called smoke.
ccArray *smoke = ccArrayNew(0);
I have a class called Smoke, which is a subclass of CCSprite, with the addition of an ivar called __opacity.
When I add a new smoke to the ccArray I do like this:
ccArrayAppendObject(smoke, [Smoke spriteWithFile: #"smoke.png"]);
[smoke->arr[smoke->num - 1] setupWithTouch: touch andOpacity: 255.0f];
[self addChild: smoke->arr[smoke->num - 1]];
Which doesn't lag at all, and is fast,
And this is how I handle the smoke every frame:
if(smoke->num > 0)
{
for(NSUInteger i = 0; i < smoke->num; i++)
{
Smoke *s = smoke->arr[i];
s.__opacity = s.__opacity - 255.0f * delta;
[s setOpacity: s.__opacity];
if(s.__opacity <= 0.0f)
{
[self removeChild: s cleanup: YES];
ccArrayFastRemoveObjectAtIndex(smoke, i);
}
}
}
When opacity is less than 0, we remove the smoke from the scene, and then
remove it from the array -- which is the part that slows the game down, removing it from the Array. It goes from 60 FPS to like 15-20 FPS, when there's like, 60 smoke particles on the scene.
Any ideas how I can speed this up?
Also, reason I'm using ccArray instead of NSMutableArray is because I read ccArray is faster.
removing object from the middle or the beginning of an array (any array) will recreate the array, and the operation is very slow (alloc+copy of members), if you have datastruct with many removes that not at the end you probably should use a linked-list
here some implementation i found in the internet (haven't tested it but it looks decent)
http://www.cocoadev.com/index.pl?DoublyLinkedList