I am creating a SpriteKit game with a tiled map. Each tile is an SKSprite node. When I have about 800 tiles, there are no problems. But if I try to increase the size of the map to around 2000 tiles, my FPS goes from 60 to 20. The number of tile nodes on the screen doesn't change (about 80), just the number of nodes off-screen. Any ideas of what could be causing this, or how to remedy it?
There doesn't appear to be a defined max number of nodes. It really depends on the amount of available free memory on your device. For example consider the following code:
int NODE_LIMIT = 375000
....
for (int i = 0; i<NODE_LIMIT; i++) {
SKNode *node = [SKNode node];
[self addChild:node];
}
I can create 375000 nodes in my sprite kit game. But as I increase the number above that, my device runs out of memory. The amount of free memory on your device will vary depending on a number of factors. As mentioned in the comments, the reason your frame rate slows down, is because the physics simulation runs even for nodes which are not visible on screen.
To maintain a high frame rate, get rid of physics bodies which are not visible, or which do not need to be simulated every frame. You could do this by adding sprites / physics bodies only when they are in the viewable part of the screen, and removing them when they are not.
Related
I am building an endless runner game where platforms appear at random intervals and then scroll right to left across the screen.
I have a sprite that jumps up vertically using
- (void)makeCharacterJump {
[self.spriteCaveman.physicsBody applyImpulse:CGVectorMake(0.0f, 80.0f)];
}
The problem is that the effect of gravity on the sprite means that it falls quite quickly and cant make the gap between the platforms.
What I would like to do is slightly slow down the effect of gravity on the falling sprite so it creates the impression of slightly floating down.
Any ideas?
If the character is the only node affected by gravity then you can change the scene’s gravity with:
self.physicsWorld.gravity = CGVectorMake(0, desiredGravity);
If it is not then you’ll have to play with the character’s physics body properties: friction, linearDamping or angularDamping values.
Hope that helps.
I've made a Tiled game. Right now I'm stress testing my phone's capabilities by increasing amount of nodes in the scene. There's physics based stuff, AI movement, Day & Night system, particles popping out here & there & plenty of other stuff going on under the hood for my scenes. What I want to know is there a performance difference in using 16x16 tiles, what I have now, versus using 32x32 tiles? These tiles are basically just an image that was added to the scene; they don't have any physics bodies or anything else of that sort. They do have properties I set upon them when making the map in Tiled, but I don't think that has any performance impact. Each map has several layers (background, vegetation, spawn points, buildings, sometimes a few more). Here is a code snippet of how tiles are rendered for 1 such layer:
if([map propertiesForGid:tileGid][#"shrub"])
{
SKSpriteNode *tile = [layer tileAtCoord:coord];
tile.name = #"vegShrub";
[self addChild:tile];
}
else if([map propertiesForGid:tileGid][#"tree"])
{
SKNode *tile = [[Breakable alloc] initWithWhole:[atlas textureNamed:#"tree"] broken:[atlas textureNamed:#"tree-stump"]];
tile.position = [self pointForCoord:coord];
[self addChild:tile];
[layer removeTileAtCoord:coord];
}
If I use 32x32 tiles over my current 16x16 tiles, will I somehow free up some memory or "relieve the load" off the system?
With tile maps, each tile is usually represented by a SKSpriteNode. So if your map is 320 x 320 and you're using 32x32 tiles, you will end up with 100 nodes. Using 16x16 tiles on the same map size will result in 400 nodes. The more nodes, the greater the load.
On another note, you should look into getting the SKAToolKit to parse tile maps in your app. It's open source, free and has a ton of built in features such as auto follow, mini map, etc...
Does anyone have any idea how I can make my SKSpriteNode defy gravity? I thought of inverting the default gravity but realise I also need things to fall too!
It seems like it should be easy but reading through the documentation I can’t see how I would do it!
Thanks
Update: In iOS 8 / OS X Yosemite (10.10), physics fields provide an elegant solution to these sorts of problems. Here's a quick take on using them to add buoyancy (for a specific set of nodes) to your scene.
Create an SKFieldNode with the linearGravityFieldWithVector constructor, providing a vector that's the opposite of gravity, and add the field node to your scene.
Set the fieldBitMask on your balloons to something unique.
Set the categoryBitMask on the field to something that overlaps with the balloons' fieldBitMask, but that does not overlap with the fieldBitMask of any other bodies.
Now, the balloons will rise or hold steady, but other objects will fall. Tweaking the field's strength will let you tune whether the balloons' buoyancy is perfectly balancing gravity (so that they float in place, but are disturbed when touched), or slightly more or less than gravity (so that they slowly rise or fall).
By default, a field is infinite, covering the whole scene, but you can change that with the field's region property to limit it to a portion of the scene. (This is useful if you want to simulate buoyancy in water — once an object rises past the top of the field at the water's surface, it falls back in.)
Also, if you want variable buoyancy as per #TheisEgeberg's answer, you can control its variation over distance with the falloff property.
In iOS 7 / OS X Mavericks (10.9), or if you want more precise control over which forces apply where and when, you can use the approach from my original answer below.
If you want an object to really float like a balloon — that is, to be buoyant, affected by gravity but also counteracting it — you'll need to apply a force to it on every frame (i.e. in your update: method).
Beware scaling: gravity is a constant acceleration, but if you're applying a force to counteract gravity, a force is proportional to mass. To make a vector that perfectly balances gravity for use in applyForce:, you'll need to:
scale the gravity vector by {-1,-1,-1} to point in the opposite direction
scale by the mass of the body you're applying the force to (F = ma, where gravity or anti-gravity is a).
scale by 150 — there's a bug where the SKPhysicsWorld.gravity property isn't in the same units as applyForce:. (If you turn SKPhysicsWorld gravity off and use SKFieldNode gravity instead, you don't need to do this.)
Unlike turning off affectedByGravity and applying an action to make the balloon rise, this approach works well with the rest of the physics sim. With a balancing force exactly equal to gravity, the balloon will float in place — after colliding with other things it'll return to equilibrium. If the balancing force is greater than gravity, the balloon will rise, but its rise will be hindered by other bodies in its way.
First off, an SKSpriteNode isn't affected by gravity at all. It is the SKPhysicsBody that belongs to the node that is affected by gravity.
Second...
myNode.physicsBody.afectedByGravity = NO;
:D
If you want it to rise upwards then you can add an action to it...
SKAction *moveAction = [SKAction moveByX:0 y:-10 duration:1];
SKAction *repeatingAction = [SKAction repeatActionForever:moveAction];
[myNode runAction:repeatingAction];
As many have said it's about counterforce.
So yes, you can apply a counterforce to the balloon to make it go upwards.
But to make it look like a balloon you need to understand what makes a balloon go up: Air pressure. Since the helium or whatever light gas you are using is lighter than air it will start to go up, or in other words the heavier air will go under the balloon. It's like a piece of wood in water, the heavier water will go under the wood, till the wood is soaked and gets even heavier than the water.
So what you should do is to make the counterforce adapt to the height of the balloon, the higher it gets the less pressure you apply upwards. This is a way to simulate buoyancy.
Here’s my answer:
- (MyClass *)newRisingObject
{
MyClass *risingObject = [MyClass spriteNodeWithImageNamed:#"image"];
[risingObject setPosition:CGPointMake(CGRectGetMidX(self.frame), CGRectGetMinY(self.frame))];
[risingObject setName:#"name"];
[risingObject setUserInteractionEnabled:YES];
// Physics
//
[risingObject setPhysicsBody:[SKPhysicsBody bodyWithCircleOfRadius:risingObject.size.width / 2.0f]];
[risingObject.physicsBody setRestitution:1.0f];
[risingObject.physicsBody setFriction:0.0f];
[risingObject.physicsBody setLinearDamping:0.0f];
[risingObject.physicsBody setAngularDamping:0.0f];
[risingObject.physicsBody setMass:1.3e-6f];
return risingObject;
}
.
- (void)didMoveToView:(SKView *)view
{
/* Setup your scene here */
MyClass *risingObject = [self newRisingObject];
[self addChild:risingObject];
}
.
- (void)update:(CFTimeInterval)currentTime
{
/* Called before each frame is rendered */
[self enumerateChildNodesWithName:#"name"
usingBlock:^(SKNode *node, BOOL *stop)
{
// Push object up
//
[node.physicsBody applyImpulse:CGVectorMake(0.0f, node.physicsBody.mass * OBJECT_ACCELERATION)];
}];
}
Inside my didbeginContact I added code like:
// increase node speed on each contact
CGVector thrustVector = CGVectorMake(_node.physicsBody.velocity.dx+100,
_node.physicsBody.velocity.dy+100);
[_node.physicsBody applyForce:thrustVector];
But it is not increasing its speed on every contact with another node. It should bounce a little bit (more) for every contact.
I also tried:
// increase node speed on each contact
_node.physicsBody.restitution = _node.physicsBody.restitution + 0.001f;
NSLog(#"current node restitution: %f", _node.physicsBody.restitution);
But when the node bounces too FAST it suddenly goes through other nodes that are suppose to be walls.
But how do I properly do that?
Instead of applying force, you might want to try applying an impulse for a more instantaneous effect:
[_node.physicsBody applyImpulse:thrustVector];
Bear in mind that you're using a vector based on the node's current velocity, so if that drops in the meantime, the impulse will be smaller too.
If the node is moving too fast, it can happen that it will pass through another body (wall) completely before the new frame is rendered, so no collision will be detected. There is a property that enables performing more expensive calculations on such fast bodies, _node.physicsBody.usesPreciseCollisionDetection = YES.
However, even that may not be satisfactory if you reach extreme velocities. You may have to implement some manual checking if the node is still within the edge walls' bounds in the -(void)didSimulatePhysics method.
I'm trying to move a sprite around a CCTMXMap in a smooth fashion. I've figured out how (using CCActions) to move from tile to tile, but I get gaps in my animation (it pauses for a frame while it reevaluates which direction to walk). I've tried moving the character in a scheduled update: method, but that gets messy when you try and restrict the sprite to only moving from tile to tile. Any suggestions on how to get the clean, consistent animation without messy manual animation using update?
Yes, don't use actions. You'll always have the 1-frame delay problem when using CCActions.
Moving the sprite in update is really pretty simple. Especially if you restrict movement to a speed (points per frame) that is clearly divisible by the tile size. For example if your tiles are 40x30, then horizontal speeds of 1,2,4,8,10 would work fine. Vertically 1,2,3,5,6,10 would work.
Update the position by this number, cast it down to int, compare it with the destination location:
if ((int)currentPos.x == (int)targetPos.x && (int)currentPos.y == (int)targetPos.y)
{
NSLog(#"I'm there!");
}
The reason for casting to int is to avoid rounding errors in floating point values.
Another solution would be - especially if your character can only move in one direction at a time - to figure out the number of frames it will take him to get there. If the character has to move 40 points to the right, and he moves at 4 points per frame, it'll take him 10 frames. Then just count the number of frames (how many times the update method ran) and if it reaches 10 (or 0 if you count down) then you know that the character has arrived without needing to check his position.