Applying an effect to a SKScene is that expensive? - ios

I am developing a game using SpriteKit. In response to the user getting a bonus, I would like to flash the screen 3 times while the play action continues. By flashing I am inverting the scene colors, but I am open to use other effect.
I have tried to use a CIFilter on the scene, but the frame rate dropped from 60 fps to 13 fps, making the game unplayable.
I have tried to use the CIFilter on the whole scene, by doing this
CIFilter * (^invert)(void) = ^ {
CIFilter *filter = [CIFilter filterWithName:#"CIColorInvert"];
[filter setDefaults];
return filter;
};
and then changing the scene
self.filter = invert();
self.shouldEnableEffects = YES;
I am a long time user of Cocos2D. I did this kind of effect on another app of mine developed with Cocos2D in the past and it had barely no impact on the frame rate compared to this miserable frame rate using SpriteKit.
I have tried to apply the effect just to the character that represents the user, that is barely 100x100 pixels. The frame rate dropped from 60 to 30fps... better but the game is also unplayable.
Any way to do this or to achieve some impressive brief effect to the whole screen as the play evolves?
thanks

I have a game where i flash the screen and the following piece of code runs smoothly, you can try it out and repeat the action as per your need :)
// Flash background if contact is detected
[self runAction:[SKAction sequence:#[[SKAction repeatAction:[SKAction sequence:#[[SKAction runBlock:^{
self.backgroundColor = [SKColor redColor];
}], [SKAction waitForDuration:0.05], [SKAction runBlock:^{
self.backgroundColor = [SKColor blueColor];
}], [SKAction waitForDuration:0.05]]] count:4], [SKAction runBlock:^{
//Do anything additional you wan to run during flash period
}]]] withKey:#"flash"];

Related

IOS SpriteKit Blurred Sprite with TexturePacker

I'm doing a project with IOS Sprite kit (Objective-C), I use TexturePacker to make my Sheet/Atlas ,In my scene view I only display a Character animation nothing more but I have an issue when I display my Sprite it is blurry.
The resolution of the image sprite are 512*512 (I think it's good), and the size of Sheets are 2048*2048.
I try many things on texturePacker to increase the quality but noting work, when I display my animation on TexturePacker directly the quality is good, but when I try on XCode, the result are blurry, I have try to play my character animation in a array animation and the result are not blurry (but this technique use to much memory) so I think the problem is TexturePacker
Has anyone had the same problem is would have a solution ?
Here is my code :
[self.scene setBackgroundColor:[UIColor clearColor]];
self.view.allowsTransparency = YES;
// load the atlas explicitly, to avoid frame rate drop when starting a new animation
self.atlas = [SKTextureAtlas atlasNamed:CHARACTER_ATLAS_NAME];
SKAction *walk = [SKAction animateWithTextures:CHARACTER_ANIM_IDLE timePerFrame:0.033];
self.sequence = [SKAction repeatActionForever:walk];
SKSpriteNode *sprite = [SKSpriteNode spriteNodeWithTexture:CHARACTER_TEX_HELLO_0];
sprite.size = CGSizeMake(self.view.frame.size.width, self.view.frame.size.height);
sprite.position = CGPointMake(sprite.size.width/2, sprite.size.height/2);
[sprite runAction:sequence];
[self addChild:sprite];
Here is a comparison on the top sprite when I use the image array and that below with SpriteKit and Texturepacker ( you can see the difference especially at eye level )
Thanks for your help

SpriteKit Texture Atlas vs Image xcassets

I am making a game and I noticed that during some scenes, my FPS kept dropping around the 55-60FPS area (using texture atlas). This drove me nuts so I decided to put all my assets to the Images.xcassets folder and voila, steady 60FPS.
I thought this was a fluke or that I was doing something wrong, so I decided to start a new project and perform some benchmarks...
Apple's documentation says that using texture atlas's will improve app performance. Basically, allowing your app to take advantage of batch rendering. However...
The Test (https://github.com/JRam13/JSGlitch):
- (void)runTest
{
SKAction *spawn = [SKAction runBlock:^{
for (int i=0; i<10; i++) {
SKSpriteNode *sprite = [SKSpriteNode spriteNodeWithImageNamed:#"Spaceship"];
sprite.xScale = 0.5;
sprite.yScale = 0.5;
sprite.position = CGPointMake(0, [self randomNumberBetweenMin:0 andMax:768]);
SKAction *action = [SKAction rotateByAngle:M_PI duration:1];
[sprite runAction:[SKAction repeatActionForever:action]];
SKAction *move = [SKAction moveByX:1200 y:0 duration:2];
[sprite runAction:move];
//notice I don't remove from parent (see test2 below)
[self addChild:sprite];
}
}];
SKAction *wait = [SKAction waitForDuration:.1];
SKAction *sequence = [SKAction sequence:#[spawn,wait]];
SKAction *repeat = [SKAction repeatActionForever:sequence];
[self runAction:repeat];
}
Results:
Tests repeatedly show that using the xcassets performed way better than the atlas counterpart in FPS. The atlas does seem to manage memory marginally better than the xcassets though.
Anybody know why these results show that images.xcassets has better performance than the atlas?
Some hypotheses I've come up with:
xcassets is just better optimized than atlasas.
atlasas are good at drawing lots of images in one draw pass, but have bigger overhead with repeated sprites. If true, this means that if your sprite appears multiple times on screen (which was the case in my original game), it is better to remove it from the atlas.
atlas sheets must be filled in order to optimize performance
Update
For this next test I went ahead and removed the sprite from parent after it goes offscreen. I also used 7 different images. We should see a huge performance gain using atlas due to the draw count but...
First, revise your test to match this :
for (int i=0; i<10; i++) {
SKSpriteNode *sprite = [SKSpriteNode spriteNodeWithImageNamed:#"Spaceship"];
sprite.xScale = 0.5;
sprite.yScale = 0.5;
float spawnY = arc4random() % 768;
sprite.position = CGPointMake(0, spawnY);
SKAction *action = [SKAction rotateByAngle:M_PI duration:1];
[sprite runAction:[SKAction repeatActionForever:action]];
SKAction *move = [SKAction moveByX:1200 y:0 duration:2];
// next three lines replace the runAction line for move
SKAction *remove = [SKAction removeFromParent];
SKAction *sequence = [SKAction sequence:#[move, remove]];
[sprite runAction:sequence];
[self addChild:sprite];
}
Rerun your tests and you should notice that your framerate NEVER deteriorates as in your tests. Your tests were basically illustrating what happens when you never remove nodes, but keep creating new ones.
Next, add the following line to your ViewController when you set up your skview :
skView.showsDrawCount = YES;
This will allow you to see the draw count and properly understand where you are getting your performance boost with SKTextureAtlas.
Now, instead of having just one image , gather 3 images and modify your test by choosing a random one of those images each time it creates a node, you can do it something like this :
NSArray *imageNames = #[#"image-0", #"image-1", #"image-2"];
NSString *imageName = imageNames[arc4random() % imageNames.count];
In your code, create your sprite with that imageName each time through the loop. ie :
SKSpriteNode *sprite = [SKSpriteNode spriteNodeWithImageNamed:imageName];
In your SKTextureAtlas test, use that same imageName obviously to create each sprite.
Now... rerun your tests and take note of the draw count in each test.
This should give you a tangible example of what batch rendering is about with SKTextureAtlas.
It's no about rendering the same sprite image thousands of times.
It's about drawing many different sprites images in the same draw pass.
There is likely some overhead in getting this rendering optimization, but I think the draw count should be self explanatory as to why that overhead is moot when all things are considered.
Now, you can hypothesize some more :)
UPDATE
As mentioned in the comments, my post was to expose why your test was not a good test for the benefits of SKTextureAtlas and was flawed if looking to analyze it in a meaningful way. Your test was like testing for measles with a mumps test.
Below is a github project that I put together to pinpoint where an SKTextureAtlas is appropriate and indeed superior to xcassets.
atlas-comparison
Just run the project on your device and then tap to toggle between tests. You can tell when it's testing with SKTextureAtlas because the draw count will be 1 and the framerate will be 60fps.
I isolated what will be optimized with a SKTextureAtlas. It's a 60 frame animation and 1600 nodes playing that animation. I offset their start frames so that they all aren't on the same frame at the same time. I also kept everything uniform for both tests, so that it's a direct comparison.
It's not accurate for someone to think that using SKTextureAtlas will just optimize all rendering. It's optimization comes by reducing draw count via batch rendering. So, if your framerate slowdown is not something that can be improved via batch rendering, SKTexture atlas is the wrong tool for the job. right ?
Similar to pooling of objects, where you can gain optimization via not creating and killing your game objects constantly, but instead reusing them from a pool of objects. However if you are not constantly creating and killing objects in your game, pooling ain't gonna optimize your game.
Based on what I saw you describe as your game's issue in the discussion log , pooling is probably the right tool for the job in your case.

Transform a physics body using Sprite Kit

I am making a 2D game where a character stands stationary at the left hand side of the screen and objects fly towards him from the right. He needs to be able to slap these flying enemies down. I have a sprite animation that contains the "slap" animation frames. During the slap, his body moves slightly and his arm rotates from resting on the ground, to fully extended above his head and then slaps down to the ground. Here is what it looks like:
SumoSmash Animation GIF
For these purposes I have a class called SumoWarrior which is a SKSpriteNode subclass. The SumoWarrior sprite node has a child sprite node called warriorArm. My idea was to have the main sumo warrior sprite node display the animation, and to use this warriorArm sprite node only for the purposes of a physics body in the shape of the warriors arm. I need to somehow rotate this arm body to follow the sprite animation, in order to detect collisions with the flying objects.
Here is how the arm is created:
sumoWarrior.warriorArm = [SKSpriteNode spriteNodeWithTexture:[SKTexture textureWithImageNamed:#"warriorArm"]];
sumoWarrior.warriorArm.position = CGPointMake(15, 25);
sumoWarrior.warriorArm.anchorPoint = CGPointMake(0.16, 0.7);
sumoWarrior.warriorArm.texture = nil;
sumoWarrior.warriorArm.physicsBody = [SKPhysicsBody bodyWithPolygonFromPath:[sumoWarrior createArmBody]];
sumoWarrior.warriorArm.physicsBody.mass = 9999;
sumoWarrior.warriorArm.physicsBody.categoryBitMask = CollisionPlayer;
sumoWarrior.warriorArm.physicsBody.collisionBitMask = CollisionEnemy;
sumoWarrior.warriorArm.physicsBody.contactTestBitMask = CollisionEnemy | CollisionAlly;
sumoWarrior.warriorArm.physicsBody.allowsRotation = YES;
sumoWarrior.warriorArm.physicsBody.dynamic = YES;
Is it possible to rotate and extend this arm somehow, in order for it to follow the animation precisely? Is it also possible to alter the main physics body of the warrior (which is just a polygon around the body, without the arm) so that IT also follow the animation? Or am I completely missing the way that this should be done?
I used Texture Packer for the texture and animation. I created an Texture Atlas called sumoAnimations and Texture Packer created a .h file for me which I then imported into the project.
You can get a free copy if you do not already have it.
Before I launch into the code, you might want to reconsider using the animation you have. Going by your previous comments the only relevant frames in the animation are frame 15, 16 and 17. I am not even sure about frame 17 because the sumo already has his hand down. That only give you 3 frames which by the animation you provided is equal to 0.1 seconds as each frame has a time of 0.05 seconds.
Take a look at the 3 pics I included to see what I mean. You might want to consider getting a new animation or allowing for greater time in between frames. I used 0.25 seconds per frame so you can see it more clearly. You can change it to anything you like.
As for the player being missing and being hit, you can create a clearColor sprite rect around the player (behind the arm of course) to detect contact of a missed object.
#import "MyScene.h"
#import "sumoAnimation.h"
#interface MyScene()<SKPhysicsContactDelegate>
#end
#implementation MyScene
{
SKSpriteNode *sumo;
SKSpriteNode *arm;
SKAction *block0;
SKAction *block1;
SKAction *block2;
SKAction *block3;
SKAction *block4;
SKAction *slapHappy;
SKAction *wait0;
SKAction *wait1;
}
-(id)initWithSize:(CGSize)size
{
if (self = [super initWithSize:size])
{
sumo = [SKSpriteNode spriteNodeWithTexture:SUMOANIMATION_TEX_SUMO_001];
sumo.anchorPoint = CGPointMake(0, 0);
sumo.position = CGPointMake(0, 0);
[self addChild:sumo];
arm = [SKSpriteNode spriteNodeWithColor:[SKColor clearColor] size:CGSizeMake(34, 14)];
arm.anchorPoint = CGPointMake(0, 0);
arm.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:CGSizeMake(34,14) center:CGPointMake(17, 7)];
arm.physicsBody.dynamic = NO;
slapHappy = [SKAction animateWithTextures:SUMOANIMATION_ANIM_SUMO timePerFrame:0.25];
// start the animation
block0 = [SKAction runBlock:^{
[sumo runAction:slapHappy];
}];
// time until frame 15 is reached
wait0 = [SKAction waitForDuration:3.50];
// add arm at frame 15 positon
block1 = [SKAction runBlock:^{
arm.position = CGPointMake(205, 125);
arm.zRotation = 1.3;
[self addChild:arm];
}];
// wait until next frame
wait1 = [SKAction waitForDuration:0.25]; // time in between frames
// move arm and rotate to frame 16 position
block2 = [SKAction runBlock:^{
arm.position = CGPointMake(224, 105);
arm.zRotation = 0.4;
}];
// move arm and rotate to frame 17 position
block3 = [SKAction runBlock:^{
arm.position = CGPointMake(215, 68);
arm.zRotation = -0.65;
}];
// remove arm from view
block4 = [SKAction runBlock:^{
[arm removeFromParent];
}];
}
return self;
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[sumo runAction:[SKAction sequence:#[block0, wait0, block1, wait1, block2, wait1, block3, wait1, block4]]];
}
-(void)update:(CFTimeInterval)currentTime
{
//
}
#end
Updated based on additional comments
The pic below outlines my suggestion. Add a physics body rect for the frames the sumo is swatting. This will allow you to not have to deal with adding a body for every frame in the precise position. It will also make the swatting more effective.
Your object can still fall to the ground and have the crushed animation play. Remember that your sumo animation moves very fast and the player will not see precise locations for each frame.
Your idea of having the arm "push" the object would take a much more precise animation. Something like the arm's position changing by a single increment. Then you would have to precisely position a body on the hand. I am not saying its impossible but its certainly A LOT of work and difficult to do.

Sprite Kit, force stop on physics

I'm creating an ongoing learning project (basically I code while learning to use the framework in hope it will be useful for me and for someone else) for Sprite Kit (you can find it here if you are interested) but I'm facing some performance problems with my code.
The project puts cubes on the screen and make them falling. Here's the class that creates the piece
// The phisics for our falling piece:
// It will be a square, subject to gravity of 5: check common.h (9.8 is way too much for us)
self.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:CGSizeMake(self.size.width, self.size.height)];
self.physicsBody.dynamic = YES;
self.physicsBody.mass = 100;
self.physicsBody.collisionBitMask = piecesCollisionBitmask;
self.physicsBody.allowsRotation = NO;
and here is the scene file with the loop.
//schedule pieces
SKAction *wait = [SKAction waitForDuration:1];
SKAction *pieceIsFalling = [SKAction runBlock:^{
FLPPiece *piece = [[FLPPiece alloc] init];
[self addChild:piece];
}];
SKAction *fallingPieces = [SKAction sequence:#[wait,pieceIsFalling]];
[self runAction:[SKAction repeatActionForever:fallingPieces]];
I suspect that physics is behind my frame rate drop. I would like to stop physics execution on node while keeping it on the screen as soon as it collides with something else.
Is that possible? How can I do that?

iOS Sprite Kit why can't I repeat colorizeWithColor using white color?

I'm experimenting with ways of selecting sprite nodes using methods other than scale. The one method that I like the most is colorize with white, which highlights the node visibly.
However, I cannot seem to be able to replicate the colorize with white behavior more than once. Why can't I apply colorizeWithColor using white color more than once?
These two method calls are identical, except for the color used. If I use red, gray, etc, the node responds by flashing for each touch. But If I use white, it does so only once, then never responds to touches again.
[self runAction:[SKAction colorizeWithColor:[SKColor lightGrayColor] colorBlendFactor:0.8 duration:0.6] completion:^{
[self runAction:[SKAction colorizeWithColorBlendFactor:0.0 duration:0.4]];
}];
[self runAction:[SKAction colorizeWithColor:[UIColor colorWithWhite:0.99 alpha:1.0] colorBlendFactor:0.8 duration:0.6] completion:^{
[self runAction:[SKAction colorizeWithColorBlendFactor:0.0 duration:0.4]];
}];
This is very interesting - and I'm not sure I have the answer. Using a white colorisation does to affect a node differently from other colours.
if you perform a colorize on a sprite node with blueColor and watch in the simulator, the colour will remain.
[node runAction:[SKAction colorizeWithColor:[SKColor blueColor] colorBlendFactor:0.8 duration:0.6]];
However, if you perform a colorize on a sprite node with whiteColor and watch in the simulator, it appears to automatically unwind (even without any completion block).
[node runAction:[SKAction colorizeWithColor:[SKColor whiteColor] colorBlendFactor:0.8 duration:0.6]];
I can find no reference to why this might be the case in documentation/header files. Still searching.
I suggest you make a method to colorize, that returns you SKAction to use, for example:
-(SKAction*)colorizeChoosenSpriteNodeWithColor:(SKColor*)color
{
SKAction *changeColorAction = [SKAction colorizeWithColor:color colorBlendFactor:1.0 duration:0.3];
SKAction *waitAction = [SKAction waitForDuration:0.2];
SKAction *startingColorAction = [SKAction colorizeWithColorBlendFactor:0.0 duration:0.3];
SKAction *selectAction = [SKAction sequence:#[changeColorAction, waitAction, startingColorAction]];
return selectAction;
}
And it is important to say if you are using SKSpriteNode that is made from SKColor or from an image. If you are trying to colorize SKSpriteNode that is made like:
SKSpriteNode *node = [[SKSpriteNode alloc]initWithColor:[SKColor redcolor] size:CGSizeMake(8, 8)];
By running colorize Action you will change that color and with that "startingColorAction" from above you will not make it to the recent color.
As it says in documentation:
"Creates an animation that animates a sprite’s color and blend factor." So by running this Action to a SKSpriteNode that is made just from SKColor it will change the color and running the action with colorblendfactor 0.0 it will do nothing.
Use this action for colorizing sprite nodes made from images. Try testing it and see what happens.
And please, please read the "Sprite Kit Programming Guide" first!

Resources