In my SpriteKit game, I have many tile nodes (>100) arranged randomly and I need to be able to detect collisions between the tiles and the character node. To do this, I use SKPhysicsBody.
I find that if I enable SKPhysicsBody code, my frame rate drops to around 40fps, but If I comment out the code, it goes up to 60fps. I guess this is something to do with the engine trying to simulate physics for 100+ nodes each frame... is there a way I can prevent this from happening but still detect collisions between my character and the tiles?
For the tile physics I'm using the following code for my tiles:
self.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:size];
self.physicsBody.affectedByGravity = NO;
self.physicsBody.categoryBitMask = WallCategory;
self.physicsBody.collisionBitMask = 0;
self.physicsBody.contactTestBitMask = CharacterCategory;
and for my character:
self.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:size];
self.physicsBody.usesPreciseCollisionDetection = YES;
self.physicsBody.restitution = 0;
self.physicsBody.friction = 0;
self.physicsBody.linearDamping = 0;
self.physicsBody.categoryBitMask = CharacterCategory;
self.physicsBody.contactTestBitMask = 0xFFFFFFFF;
self.physicsBody.collisionBitMask = BoundaryCategory;
Your low frame rate could be caused by other contributing factors but without setting your entire code, it's hard to localize the main culprit.
If feasible for your game structure, you could consider disabling the physicsBody and running a contact check yourself. You would have to create an array of all your tile objects and then use intersectsNode: from the SKNode Class to check for any contact between player and object.
There are a few reasons this could be happening.
First, the simulator does not simulate collisions as a real iPhone would. If you are using the simulator alone, I strongly suggest that you try on an actual device.
Secondly, you should assert that your app's assets are not loaded in an inefficient way such as lazy loading. The assets should be loaded within an asynchronous call to the background thread upon launch. For more detail on this, check out an example project by Apple.
Third, you have not posted your - (void)didBeginContact:(SKPhysicsContact *)contact method. If implemented inefficiently or incorrectly, it can cause laggy collisions.
You should also be profiling your app in Instruments, so you can determine exactly where and when the drop in FPS is happening
Note: the property usesPreciseCollisionDetection may be contributing to a significant amount of overhead, given that you are creating >100 nodes on screen.
Related
I am developing a game with swift Sprite kit. But I've got a problem, as you can see in the picture I have a number of physical blocks with the same size aligned. When I slide this block above the others sometimes he gets stuck or do small jumps.
It seems that the physical bodies sometimes overlap.
Anyone know how I can fix it to have a continuous and without inprecision movement?
Some physics characteristics:
player.physicsBody?.friction = 0.0
player.physicsBody?.restitution = 0.00
player.physicsBody?.linearDamping = 0.1
player.physicsBody?.angularDamping = 0.0
player.physicsBody?.allowsRotation = false
player.physicsBody?.velocity.dx = 0
player.physicsBody?.velocity.dy = 0
player.physicsBody?.categoryBitMask = heroCategory
player.physicsBody?.contactTestBitMask = enemyCategory
player.physicsBody?.density = 2.3
The problem appears to be just a slight imperfection of physics engine (and hey, it's a simulation, and not completely accurate, I've run into this kind of thing before). I see that you are preventing rotation of the player, so you can change the physics body of the square player into a circle. This should be something like:
player.physicsBody = SKPhysicsBody(circleOfRadius: 10)
Replace 10 with the proper radius of your player. This should smooth out the bumps you are encountering.
How are you sliding the block?
I've had the same problem and I was able to solve it sliding my hero using SKAction to run a code block that changes the hero's velocity or by just simple running an SKAction.moveBy. Your SKAction should'nt be attempting to push your block against the ground. An action to move the block only in the x direction.
You can also smooth the surface by making sure your physicsBody(s) of ground blocks are not overlapping. Introduce a gap of 1-2 points. Or maybe group tiles with a single physicsBody. This may help as well.
I'm trying to get into game dev with SpriteKit for iOS, and I'm following a book called iOS Games By Tutorials by Ray Wenderleich.
Right now I'm working on a platform game, but I'm getting some weird results when trying to do something not in the book.
Basically I have a functioning platform game that loads the level from a JSON-file, and lays out bricks/ nodes in a background layer. I want to have multiple floors, so when a user jumps through a portal in the game, a new floor/ level is loaded and placed on screen in the exact same position (all floors are the same size).
I didn't think I would have any troubles with this, as being notified when the user jumps through the portal is really easy. And I'm already loading a level to begin with, so loading another one at this time and replacing the original one shouldn't be hard.
However, it's proving to be very difficult. When the game loads I call this method:
- (void)createWorld {
_bgLayer = [self createScenery:[NSNumber numberWithInteger:1]];
_worldNode = [SKNode node];
[_worldNode addChild:_bgLayer];
[self addChild:_worldNode];
self.anchorPoint = CGPointMake(0.5, 0.5);
_worldNode.position = CGPointMake(-_bgLayer.layerSize.width / 2, -_bgLayer.layerSize.height / 2);
}
This initializes a level (floor 1) from a JSON file, which basically is just a number of 32x32 nodes positioned next to each other forming a map. It then creates a worldNode, adds the bgLayer to the worldNode, and then adds it to the screen. Later it centers the worldNode on the screen. The worldNode is the node that moves when the player moves, the bgLayer is always stationary.
When the user jumps through a portal, I call this method:
- (void)goingUp {
if (_isChangingFloor) return;
_isChangingFloor = YES;
[_bgLayer removeFromParent];
_bgLayer = nil;
[_worldNode removeFromParent];
_worldNode = nil;
_bgLayer = [self createScenery:[NSNumber numberWithInteger:0]];
_worldNode = [SKNode node];
[_worldNode addChild:_bgLayer];
[self addChild:_worldNode];
_worldNode.position = CGPointMake(-_bgLayer.layerSize.width / 2, -_bgLayer.layerSize.height / 2);
}
This method wasn't always this "full", as I thought I could just replace bgLayer with the new one. That didn't work, so now I'm trying to reset everything, to replicate what happens in the createWorld method at start. However, this doesn't work either...
This is the result I get after jumping through the portal:
As you can see, there's 1 node on-screen, however the debug-info says there are 446. I believe this is the total number of nodes on the entire floor, but only around 90-100 should be visible at a time. I've tried adding/ removing nodes from the level, which increases/ decreases this number.
So as you all probably can understand, I'm really confused. Why doesn't it simply replace the old background with a new one when I jump through a portal? There's nothing wrong with the JSON-file itself, as I've tried loading floor0 in at start, and that works fine. Why isn't the behavior the same when jumping through the portal as when I initially load the game? What is different here? Why does it say 446 nodes on-screen when there's clearly only 1. Why is there only 1 node on the screen?
I've been stuck on this for several days now, and I would really appreciate any help that would get me closer to a solution. Thanks in advance!
i think you should check the zposition of _bgLayer it wld be higher index every time you remove and add it may be this would reslove your problem
i see that your scene has anchor point in the middle of screen
should nod your world node position be CGPointZero to make the world be in center of scene?
_worldNode.position = CGPointZero;
instead of
_worldNode.position = CGPointMake(-_bgLayer.layerSize.width / 2, -_bgLayer.layerSize.height / 2);
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)];
}];
}
Working on an iOS game using SpriteKit. My background is made up of map tiles (essentially an infinite map, procedurally generated).
Our system is designed to manage "chunks" of the map, and we only load chunks near the player. Since SpriteKit requires we add SKSpriteNodes, we no longer have clean control over "unloading" sprites for chunks/tiles that are no longer near the player.
I realize SpriteKit won't actually render things off-screen, but it's going to kill performance if we can't remove sprites no longer needed, or check if a chunk/tile is already added.
Since SKNodes doesn't respond to isEqual:, I only see two ways to do this:
Give each sprite a name with their chunk/tile coordinate, and check this name each update
Maintain a separate array of loaded tiles and check that instead
Is there any easier way of checking/removing if a sprite has been added already? Maybe a partial string match on sprite name?
I'm not sure that using SpriteKit is the best solution (Xcode simulator seems to drag at 30fps, have yet to test on a real device). We originally built this game in Java and we're rendering our own textures - hence only what was loaded and could be fed into opengl manually.
-(void) renderToScene:(SKScene *)scene {
for( Chunk *chunk in loadedChunks ){
for( Tile *tile in [chunk getTiles] ){
SKSpriteNode *sprite = [SKSpriteNode spriteNodeWithTexture:tileTexture];
sprite.name = #"Tile";
sprite.position = CGPointMake(realX,realY);
[scene addChild:sprite];
}
}
}
What will in fact kill your framerate is frequently creating and removing nodes - you should have a pool of sprites that you re-use rather than recreate.
Just update the sprite's texture, position and other attributes when reusing one of those you no longer need. A common use case is to have enough tiles to span the entire screen plus one row and one column, so that when an entire row or column has moved outside the screen you can reposition it at the other side with new textures according to the map data.
If i'm understanding what you're asking correctly. You want to properly remove a node/sprite from the scene if its no longer within view.
You should just be able to call the [self removeFromParent] method to remove in whenever its outside the bounds of the screen. Remember you can call this method on any object as long as its a child.
For instance if i had character,
SKSpriteNode *character;
SKSpriteNode *sword;
//all the rest of the properties are up to you.
if my character picked up a sword in the game and after a certain time period the sword is no longer accessible. I would do:
[character addChild:sword];
which would add it to the character.
[sword removeFromParent];
when i no longer need the sword to be apart of its parent.
I'm working on a piece of code, and I want there to be gravity in the view but I want specific projectiles to defy gravity and fly across the screen. I know to get rid of the gravity on the whole view its just:
self.physicsWorld.gravity = CGVectorMake(0, 0);
But as stated I want gravity on the scene.
So I'm wondering if there is a way to take the gravity off one specific item? (i.e. the SKSpriteNode _debris item in my case)
By setting the physicsBody to not be affected by gravity.
E.g.
myNoGravityObject.physicsBody.affectedByGravity = NO;
See the documentation of SKPhysicsBody.
If you don't want a node to interact with physics calculations at all, but still want it to have a physicsBody (i.e. to make it begin to interact with things later on, or to check for collisions with other nodes), you can set
node.physicsBody.dynamic = NO;
This will cause the node to ignore gravity, as well as collisions, impulses, and the like. If you are setting up the contact delegate, note that at least one node in any given contact must be dynamic for the contact delegate to be notified of the event.