I've returned to using Cocos2d after an 18 month gap. My initial source for guidance has been the learn Cocos2d book by Steffen Itterheim and Andreas Low.
I have to say I've found the guide rather intuitive to follow but have hit a couple of issues.
The first is the animation of the sprites. I am without a doubt using the HD sprites correctly (this has been proved by renaming the HD sprite and getting warnings that the HD sprite atlas isn't available).
When the sprites animate, they blur. At first I thought this was a simulator issue, however, while testing on my iPhone 5 and my partners iPhone 4, the same blurring is experienced. The method I have used for updating the graphics is as follows: -
-(void) update:(ccTime)delta
{
if (self.parent.visible)
{
CGSize screenSize = [CCDirector sharedDirector].winSize;
if (self.parent.position.x > - 50)//screenSize.width * 0.5f)
{
self.parent.position = ccpAdd(self.parent.position, ccpMult(velocity, delta));
}
else
{
self.parent.visible = NO; // We've gone off screen so set to invisible.
}
}
}
This is invoked with the following method : -
-(id) initWithRate:(int)rate
{
if ((self = [super init]))
{
velocity = CGPointMake(rate, 0); //-100
[self scheduleUpdate];
}
return self;
}
This is used within a "StandardMoveComponent" class, which is added to the sprite node. I should add, the graphics all come from 2 sprite sheets. They're in separate batch notes, but added to the same frameCache.
If anyone out there has any suggestions what I can do to prevent the blurring, I'd be very grateful.
Related
I have a game built in SpriteKit that I occasionally work on. I started working on it when iOS 8 was still the latest version of iOS. Back then, it always ran at 60fps or almost (on a physical device). However, since iOS 9, and now on iOS 10, the game runs at a measly 12fps on my iPhone 5. It actually usually runs faster on the simulator (13 - 14fps), which is unheard of.
I did an analysis with Instruments and it appears that some of the slowness comes from the app doing two enumerations every frame. Here they are:
-(void)moveBackground
{
[self enumerateChildNodesWithName:#"stars" usingBlock:^(SKNode *node, BOOL *stop)
{
SKSpriteNode *bg = (SKSpriteNode *)node;
CGPoint bgVelocity = CGPointMake(0, -150); //The speed at which the background image will move
CGPoint amountToMove = CGPointMultiplyScalar (bgVelocity,0.08);
bg.position = CGPointAdd(bg.position,amountToMove);
if (bg.position.y <= 0)
{
bg.position = CGPointMake(bg.position.x, bg.position.y + bg.size.height/2);
}
}];
[self enumerateChildNodesWithName:#"opbg" usingBlock:^(SKNode *node, BOOL *stop)
{
SKSpriteNode *opbg = (SKSpriteNode *)node;
CGPoint bgVelocity = CGPointMake(0, -150); //The speed at which the background image will move
CGPoint amountToMove = CGPointMultiplyScalar (bgVelocity,0.08);
opbg.position = CGPointAdd(opbg.position,amountToMove);
if (opbg.position.y <= 0)
{
[self.opbg removeFromParent];
}
}];
}
These enumerations move the starting background until it is offscreen, then remove it from the parent, and cycle the regular background once that one is done. Is there a more efficient way of doing this? Additionally, is there another way of optimizing the app without losing any quality? My images are relatively large (background is 1080x3840), but I'm not sure if I can use smaller ones and then upscale them without losing quality.
Any help improving my framerate is appreciated, and I can show more code if needed.
Thanks
The biggest change between iOS 8 and 9 was the addition of a cameranode:
https://developer.apple.com/reference/spritekit/skcameranode
And an infamous amount of performance degradation.
There's a litany of Apple forum complaints about performance on iOS 9. There was no communication from Apple in a timely and satisfactory manner. Many SpriteKit users and potential users were lost as a result of the lack of meaningful fixes and explanations.
In your case, switching to use of the camera to do your parallaxes might regain some performance.
Here's some of the issues regarding performance being discussed, so you might see if anyone was doing something similar:
https://forums.developer.apple.com/thread/14487
https://forums.developer.apple.com/thread/30651
I want to do a little game for IOS-devices with Cocos2d v2.x. One of it's features is circular map scrolling - it's like when you scroll the screen to the most right edge of map, it jumps back to the left-most side. Like you rotate the globe.
My problem: in some point of time i has to render map (all the game graphics) twice - when you move screen to the most right edge and even bit further one half of screen should display a piece of map left-end, and half should display right-end of map. How to achieve it with less pain?
Map is sub-class of CCNode with lots of childs and some custom draw methods.
I see two ways:
1. Just make a copy of map-CCNode and render it near first one:
[---map---][---map-copy---]
|-/screen/-|
Make CCRenderTexture, render map to it and display part of map + part of RenderTexture.
[----map----][RenderTex]
|-/ screen /-]
Both of my ideas has disadvantages:
CCRenderTexture horribly slows performance, even on iPad-4 full-screen-size renderTexture works at about 20fps only;
second copy of map root-CCNode will eat much of memory, especially if it has lots of children;
Maybe is there a way of rendering root-map CCNode second time with applied offset? Like:
[self offsetMap:deltaX];
[self.map vizit];
?
Thank for ideas!
quite an easy )
class worldMap : CCNode;
#interface mainScene : CCNode
{
-(id)init;
}
#property worldMap theMap;
#end
#implementation
-(id)init
{
... usual blah-blah here
[self addChild:theMap];
}
-(void)visit
{
[super visit];
CGPoint oldMapPos = self.theMap.position;
CGPoint newMapPos = ccp(oldMapPos.x + self.theMap.width, oldMapPos.y);
self.theMap.position = newMapPos;
[self.theMap visit];
self.theMap.position = oldMapPos;
}
#end
I have a SKSpriteNode called "SpikyRedBall" which is a red ball. I wanted to add spikes to it so I used the following code. I can see the Spike attached to the ball but when the ball collides with another ball it does not take the fixed joints into consideration and moves them separately. I am using the following implementation:
#implementation SpikyRedBall
-(instancetype) init
{
self = [super init];
[self attachSpikes];
return self;
}
-(void) attachSpikes
{
Spike *spike = [[Spike alloc] init];
spike.position = CGPointMake(0, 0);
// attach the joint
SKPhysicsJointFixed *ballAndSpikeJointFixed = [SKPhysicsJointFixed jointWithBodyA:self.physicsBody bodyB:spike.physicsBody anchor:CGPointZero];
[self.scene.physicsWorld addJoint:ballAndSpikeJointFixed];
[self addChild:spike];
}
#end
It sounds like you don't have collision or contact categories setup for the spikes themselves. I would try setting all physicsBody properties on the spikes to be identical to those of the balls, but obviously ensuring that they don't have collision or contact categories setup in a way that they would collide with their own parent ball.
If you can require iOS 7.1, you could use +bodyWithBodies: instead of attaching any joints.
Why don't you just add the spikes to the sprite image? If they need to disappear or fall off you can just create multiple versions of the image without spikes.
Using the sprite kit template that comes with Xcode, I modify the scene to be as follows :
#import "MyScene.h"
#interface MyScene ()
#property (nonatomic,strong)SKNode *floor;
#end
#implementation MyScene
-(id)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
/* Setup your scene here */
}
return self;
}
-(void)update:(CFTimeInterval)currentTime {
/* Called before each frame is rendered */
[self removeAllChildren];
self.floor = nil;
self.floor = [SKNode node];
CGMutablePathRef path = CGPathCreateMutable();
CGPathMoveToPoint(path, nil, 0, 10);
for(int i = 2; i<self.frame.size.width; i+=2)
{
CGPathAddLineToPoint(path, nil, i, 10);
}
self.floor.physicsBody = [SKPhysicsBody bodyWithEdgeChainFromPath:path];
SKShapeNode *shape = [SKShapeNode node];
shape.path = path;
shape.strokeColor = [UIColor redColor];
[self.floor addChild:shape];
[self addChild:self.floor];
CGPathRelease(path);
}
#end
The app seems to keep using more memory, until it either hangs or crashes (after reaching about 180MB). Using the leaks and allocations tools, I have found the following:
Leaks:
Allocations:
As can be seen from the images, there are a large number of Malloc calls using memory. I do not call Malloc directly - it seems these calls are made by SpriteKit. Likewise, there are a number of memory leaks, which also seem to be due to SKShapeNode, SKNode or other Sprite Kit objects.
How do I work around or solve this memory(leak) problem? I have to create SKShapeNodes, and SKNodes every frame. This code is just a sample to illustrate the problem - my actual project is much more complex with dynamically generated paths (not static like the one in this example).
This is a bug in sprite kit. The problem isn't just with SKShapeNode, it is also with SKPhysicsBody.
Creating either a physics body, or a shape, using a CGPath causes a memory leak.
You can verify this by commenting out either the physics body, or the shape, and running instruments with leaks, allocations and memory monitor.
Even if you release the path properly, sprite kit doesn't internally. Nothing you can do!
Fire up instruments, and watch the memory grow! XD
EDIT: This bug was present in iOS 7.0. Whether it has been fixed in 7.1 or later is not known to me as I stopped using SpriteKit because of this bug. One can verify if it is fixed by simply testing it.
I became aware of this issue while reading another post. I messed around with SKShapeNode a bit and indeed verified the memory leak issue pinpointed here.
While doing that, I had an idea...
Not really a new idea, more of a repurposed one. This wonderful idea actually allowed me to use SKShapeNodes to my hearts content :)
POOLING
Yep... I just created a pool of SKShapeNodes that I reused as needed. What a difference that makes :)
You simply redefine the path whenever needed, when done using return to your pool, and it'll be waiting there for you to play with again at a later time.
Create a ivar or property NSMutableArray in your SKScene called pool and create it when you init the SKScene. You can either populate the array with your shape nodes during init, or you can create them as needed.
This is something quick method I created for grabbing a new node from the pool :
-(SKShapeNode *)getShapeNode
{
if (pool.count > 0)
{
SKShapeNode *shape = pool[0];
[pool removeObject:shape];
return shape;
}
// if there is not any nodes left in the pool, create a new one to return
SKShapeNode *shape = [SKShapeNode node];
return shape;
}
So wherever in the scene you need a SKShapeNode you'd do this :
SKShapeNode *shape = [self getShapeNode];
// do whatever you need to do with the instance
When you are done using the shape node, just return it to the pool and set the path to . For example :
[pool addObject:shape];
[shape removeFromParent];
shape.path = NULL;
I know it's a workaround and not an ideal solution, but certainly this is a very viable workaround for anyone wanting to use a large number of SKShapeNodes and not bleed memory.
I'm using iOS 7.1.1 and had the same problem. However, by setting the SKShapeNode's path property to nil before removing a shape from a parent did fix this issue - no more leaks.
I've the same problem.
Scene added one compound node (SKShape with SKSprite) then I removed this node from scene and added again(I got leak two nodes).
My leak is fixed with recreation compound node after remove from scene
Has someone of you ever noticed that scaling a sprite up to more than 100% can cause the render time per frame to increase and never go down again?
I've set up a test project based on a cocos2d 2.0 template (I also tested it with 2.1 and it also does happen). When touching the screen (testing with an iPad 3) it creates 100 sprites that are scaled by 1.5
When there are 5000 it removes them all. When they get removed the render time very often stays at 0.016. By double tapping the home button (causing some interrupt to happen) you can make it go back to 0.001 (when there are no sprites).
I did a lot of testing when and how to cause this and I came to the conclusion that it only happens when scaling something.
While in 0.016 "mode" there is a constant memory increase (look at the allocations tool) and as soon as you double tap the home button it goes down again slowly. These allocations come from the gyroscope / accelerometer code that I
put in there. When in 0.001 "mode" it works fine but as soon as the render time shows 0.016 memory gets allocated endlessly and it is never freed.
Especially with really big scalings this thing can be achieved easily. Does anyone have an idea how to fix this?
This does happen when touching the screen in my test project. You can download it here:
https://dl.dropboxusercontent.com/u/40859730/RenderTimeIssue.zip
static int spriteCount = 0;
- (BOOL)ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event {
for (int i = 0; i < 100; i++) {
CCSprite *icon = [CCSprite spriteWithFile: #"Icon.png"];
[icon setPosition: ccp(arc4random() % 1000, arc4random() % 1000)];
[self addChild: icon];
icon.tag = spriteCount;
// this line causes the "0.016 bug", comment it out and the frame rate does go back to 0.001 when 5000 sprites are reached
icon.scale = 1.5f; // while scaling up more than 1.0 causes the problem, scaling down does not
spriteCount++;
}
if (spriteCount >= 5000) {
for (int i = 0; i < spriteCount; i++) {
[self removeChildByTag: i cleanup: YES];
}
spriteCount = 0;
}
return YES;
}
EDIT: Here are some images that show the memory increase:
http://imgur.com/a/Jy70a
I've experienced this problem on cocos2d 2.0.
But when i've upgraded to 2.1, this dont happen anymore.
Try to upgrade cocos2d to 2.1 version. There's a lot of performance improvements on rendering images.