Sprite Kit low performance with multiple nodes in one location - ios

I have set up a scene with 200 sprite nodes and place them randomly on the screen like this:
for(int i = 0; i < bubbleCount; i++) {
CGSize size = [self getRandomSize];
SKSpriteNode *bubble = [SKSpriteNode spriteNodeWithTexture:texture size:size];
bubble.anchorPoint = CGPointMake(0.5, 0.5);
bubble.position = [self getRandomPosition];
bubble.name = BUBBLE;
SKPhysicsBody *physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:size.width/2];
physicsBody.dynamic = YES;
physicsBody.affectedByGravity = NO;
physicsBody.allowsRotation = NO;
physicsBody.categoryBitMask = 0;
physicsBody.contactTestBitMask = 0;
physicsBody.collisionBitMask = 0;
bubble.physicsBody = physicsBody;
[self addChild:bubble];
}
As long as nodes keep distance from each other I get stable 60 fps. When I apply force so that nodes start moving towards the center of the scene and overlap each other, performance decreases dramatically to 2-3 fps. I guess it is related to collision detection and not rendering (if I initialize physics body with bigger radius, performance is very low already at the beginning). I have set category, contact and collision masks to 0, but it does not help.

Your best bet may be to pre-render this scenario instead of forcing the iPhone to go through 200 bodies of collision detection, which will undoubtedly cause strain.

Related

Stopping bounce when repeatedly jumping in spritekit

I have a player character and a ground tile. On both physics bodies, I have restitution set to 0.
To jump, I directly set the velocity.dy of the player. When the player comes back to the ground due to gravity, for the most part it works, or seems to.
Problems occur when I repeatedly jump. If I jump right as the player lands, there's some bounce and the height the player reaches on the next bounce doesn't always match the initial bounce.
I've tried various ways to force the velocity.dy to 0 when the user lands, but nothing fixes the weird jumping issue. How can I properly and smoothly have a consistent physics jump?
Honestly, I am not sure what you are trying to accomplish. Normally we shouldn't mess with velocities of objects. In a typical Spritekit game, we must treat it like a "real world" situation and generally apply force or impulse on the object.
I suspect you are trying to make a Mario-like game. All you have to do is apply a big enough gravity to the physicsworld and apply impulse on the sprite to jump (on touchesBegan delegate).
Just now I went ahead and made a simple mario jump scenario in Spritekit. And this is what I ended up with by setting gravity -30 for the y-component and impulse y=100 on the mario sprite. (Frame rate is bad. Looks better on simulator/device)
PhysicsWorld setup:
[self.physicsWorld setGravity:CGVectorMake(0, -30)];
self.physicsWorld.contactDelegate = self;
Mario and platform sprite setup code:
SKSpriteNode *platform = [SKSpriteNode spriteNodeWithImageNamed:#"platform"];
platform.position = CGPointMake(view.frame.size.width/2.0,0);
platform.name = #"platform";
platform.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:platform.frame.size];
platform.physicsBody.categoryBitMask = platformCategory;
platform.physicsBody.dynamic = NO;
platform.physicsBody.usesPreciseCollisionDetection = YES;
platform.physicsBody.affectedByGravity = NO;
[self addChild:platform];
SKSpriteNode *mario = [SKSpriteNode spriteNodeWithImageNamed:#"mario"];
mario.position = CGPointMake(view.frame.size.width/2.0, 400);
mario.name = #"mario";
mario.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:mario.frame.size];
mario.physicsBody.categoryBitMask = marioCategory;
mario.physicsBody.dynamic = YES;
mario.physicsBody.usesPreciseCollisionDetection = YES;
mario.physicsBody.contactTestBitMask = platformCategory;
mario.physicsBody.affectedByGravity = YES;
[self addChild:mario];
touchesBegan:
SKSpriteNode *mario = (SKSpriteNode*)[self childNodeWithName:#"mario"];
[mario.physicsBody applyImpulse:CGVectorMake(0, 100)];

SpriteKit physics giving different results each time

In my game I'm creating some balls which are effected by gravity. When game starts they fall from out of screen and get their places at the bottom. No other force is applied to them.
The problem is I'm creating these balls with exact coordinates, exact physics attributes like previous one but the result is not the same. It is similar but not same. But I think it should be exactly the same because in every time the values are same.
You can understand what I've said in these 3 pictures below.
Do you have any idea why this is happening? How can I solve it?
This is how I create the sprite nodes:
BallSpriteNode *sprite = [BallSpriteNode spriteNodeWithTexture:ballTexture];
sprite.xScale = scale;
sprite.yScale = scale;
sprite.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:sprite.size.width/2];
sprite.physicsBody.density = 1.0f;
sprite.physicsBody.restitution = 0;
sprite.physicsBody.dynamic = YES;
sprite.physicsBody.categoryBitMask = ballHitCategory;
sprite.physicsBody.contactTestBitMask = ballHitCategory;
sprite.physicsBody.collisionBitMask = ballHitCategory;
CGPoint startPosition = CGPointMake(xPosition, yPosition);
sprite.position = startPosition;
[bounceScene addChild:sprite];
There are lots of factors involved in making a physics engine less random.
The FPS greatly affects the randomness. In SKSpriteKit Physics, you have a Update method in the SKScene, which is called once every frame and carry the time difference from the previous frames. Usually to make it less random you have to somehow override the method.
Or use other Physics engine's which are customisable (Bullet Physics). It is very easy to get same result by only adjusting the StepSimulation function call.

Using Sprite Kit to draw multiples of the same sprite as a background

So what I am trying to do is use a holder (the SKSpriteNode *sprite) and load from a set of three sprites:
coral
water
base.
What I intend to do is based on previously created NSMutableArrays, draw that particular texture at positions across the iPad screen so that, for instance:
at position 1,6: water
at position 5,18: coral.
The problem that is happening is that it is only drawing one texture and no others. Is there any fix for this? Should I go about this a different way?
// Load the sprites
SKSpriteNode *sprite;
TerrainType ter;
for (int i = 0; i < numberOfRows; i++)
{
innerArray = [terrainArray objectAtIndex:i];
for (int j = 0; j < rowLength; j++)
{
ter = [[innerArray objectAtIndex:j] intValue];
sprite = [[SKSpriteNode alloc] init];
switch (ter)
{
case base1:
sprite = [SKSpriteNode spriteNodeWithImageNamed:#"MidBase"];
break;
case base2:
sprite = [SKSpriteNode spriteNodeWithImageNamed:#"MidBase"];
break;
case coral:
sprite = [SKSpriteNode spriteNodeWithImageNamed:#"Coral"];
break;
default:
sprite = [SKSpriteNode spriteNodeWithImageNamed:#"PureWater"];
break;
}
sprite.position = CGPointMake(rowLength + width30, numberOfRows + height30);
[self addChild:sprite];
}
}
It looks like you are loading all the Sprites in the same position:
sprite.position = CGPointMake(rowLength + width30, numberOfRows + height30);
You are creating an identical CGPointMake for all of them (not sure what width30 and height30 are but they don't seem to change/increase anywhere in your code). If the Sprites are also of the same size they will be overlaid one up on the other. And you end up seeing only the last one loaded.
I assume you want to make this position be related to the two for loops indexes j and i in some way.

Why won't this SKPhysicsJointPin keep these 2 sprites together?

I clearly don't understand the SKPhysicsJoint very well, but there is so little info on the web yet, other than the Apple docs of course. What is wrong with the following code, which I would think should keep the head and neck permanently joined - my intention is that they act like 2 pieces of paper with a pin, so that they can rotate a bit, but not just totally come apart. When I run this code, they fall to the bottom of the SKScene they're in, hit the ground, then the head falls off the body.
Maybe the joint is not moving WITH them or something, it's just staying in place while they move??
self.head = [SKSpriteNode spriteNodeWithImageNamed:#"head.png"];
self.head.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:self.head.size];
self.head.physicsBody.mass = 0.05;
self.head.physicsBody.dynamic = YES;
self.chest = [SKSpriteNode spriteNodeWithImageNamed:#"chest_neck"];
self.chest.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:self.chest.size];
self.chest.physicsBody.mass = 0.05;
self.chest.physicsBody.dynamic = YES;
self.leftLeg = [SKSpriteNode spriteNodeWithImageNamed:#"left_leg"];
self.leftLeg.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:self.leftLeg.size];
self.leftLeg.physicsBody.mass = 10;
self.leftLeg.physicsBody.dynamic = YES;
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
self.head.position = CGPointMake(282, 220);
self.chest.position = CGPointMake(282, 130);
self.leftLeg.position = CGPointMake(282, 10);
} else {
self.head.position = CGPointMake(512, 380);
self.chest.position = CGPointMake(512, 290);
self.leftLeg.position = CGPointMake(512, 10);
}
[self addChild:self.head];
[self addChild:self.chest];
[self addChild:self.leftLeg];
self.chestJointPinAnchor = CGPointMake(self.chest.position.x, self.chest.position.y+39);
self.chestJointPin = [SKPhysicsJointPin jointWithBodyA:self.head.physicsBody bodyB:self.chest.physicsBody anchor:self.chestJointPinAnchor];
[self.physicsWorld addJoint:self.chestJointPin];
This is because you set sprite's position after you set up its physicsBody property.
I haven't discovered any mention of that in documentation, but I broke my head last weekend trying to figure out why my manually created rope works, but recursively one doesn't.
SpriteNodes' position MUST be set before physicsBody.
So, just reorder your code somehow like that:
self.head = [SKSpriteNode spriteNodeWithImageNamed:#"head.png"];
self.chest = [SKSpriteNode spriteNodeWithImageNamed:#"chest_neck"];
self.leftLeg = [SKSpriteNode spriteNodeWithImageNamed:#"left_leg"];
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
self.head.position = CGPointMake(282, 220);
self.chest.position = CGPointMake(282, 130);
self.leftLeg.position = CGPointMake(282, 10);
} else {
self.head.position = CGPointMake(512, 380);
self.chest.position = CGPointMake(512, 290);
self.leftLeg.position = CGPointMake(512, 10);
self.head.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:self.head.size];
self.head.physicsBody.mass = 0.05;
self.head.physicsBody.dynamic = YES;
self.chest.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:self.chest.size];
self.chest.physicsBody.mass = 0.05;
self.chest.physicsBody.dynamic = YES;
self.leftLeg.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:self.leftLeg.size];
self.leftLeg.physicsBody.mass = 10;
self.leftLeg.physicsBody.dynamic = YES;
}
Oh, I've noticed, you've already found an answer by yourself... Could you please mark your question as answered then.
That seems about right.
A pin joint allows both bodies to rotate around the joint's anchor point. A real world example is an analog clock. Or a bicycle's pedals. Or a car wheel's axle.
One thing you have to know is that bodies connected through a joint will not collide with each other. You can use the pin joint limits though to prevent the head from doing a full 360 spin around the pin joint anchor.
Okay, so I found out that this is an actual bug in Sprite Kit. The fix is to set the sprite's position before setting its physicsBody. I did that and it worked perfectly, as expected.
Additionally, not invalidating what was said above, but apparently the physics engine assumes the scene's anchorPoint is left at (0, 0). If you change it to something else, the physics engine will still treat it like (0, 0).

Is it possible to rotate a Node around an arbitrary point in SpriteKit?

Is there a way to rotate a Node in SpriteKit around an arbitrary point?
I now I can manipulate the anchorPoint of my Node, but that is not sufficient if the rotation point I want to use lies outside of the Node.
What is the best way to achieve this kind of rotation in SpriteKit?
Since you're asking for the best way, here's one that works well (best is subjective):
Create an SKNode and set its position to the center of rotation. Add the node that should rotate around that center as child to the center node. Set the child node's position to the desired offset (ie radius, say x + 100). Change the rotation property of the center node to make the child node(s) rotate around the center point. The same works for cocos2d btw.
I was also trying to solve this problem a few weeks back, and did not implement the anchor points solution because I did not want to have to worry about removing the anchor point when lets say the object collides with another node and should leave its orbit and bounce away.
Instead, I came up with two solutions, both of which work if tweaked. The first took a long time to perfect, and is still not perfect. It involves calculating a certain number of points around a center position offset by a set radius, and then if a certain object comes in a certain distance of the center point, it will continually use physics to send the object on a trajectory path along the "circumference" of the circle, points that it calculated (see above).
There are two ways of calculating points with a radius
The first uses the pythagorean theorem, and the second ultimately uses trigonometry proper.
In the first, you increment a for loop by a certain amount, while it is less that 361 (degree), and for each iteration of the loop, calculate using sine and cosine a point with that angle at a certain radius from the center point.
The second uses the pythagorean theorem, and its code is below:
After you calculate points, you should create a scheduled selector [<object> scheduled selector...]; or a timer in your didMoveToView, or use a fixed update method, in addition to an instance variable called int which will hold the index of the next location to which your object will move. Every time the timer method is called, it will move the object to the next point in your calculate points array using your own or the below code labeled physicsMovement; You can play around with the physics values, and even the frequency of the ttimer for different movement effects. Just make sure that you are getting the index right.
Also, for more realism, I used a method which calculates the closest point in the array of calculated point to the object, which is called only once the collision begins. It is also below labeled nearestPointGoTo.
If you need any more help, just say so in the comments.
Keep Hacking!
I used the second, and here is the source code for it:
The code itself didn't go through
Second point calculation option
+(NSArray *)calculatePoints:(CGPoint)point withRadius:(CGFloat)radius numberOfPoints: (int)numberOfPoints{ //or sprite kit equivalent thereof
// [drawNode clear];
NSMutableArray *points = [[NSMutableArray alloc]init];
for (int j = 1; j < 5; j++) {
float currentDistance;
float myRadius = radius;
float xAdd;
float yAdd;
int xMultiplier;
int yMultiplier;
CCColor *color = [[CCColor alloc]init]; //Will be used later to draw the position of the node, for debugging only
for (int i = 0; i < numberOfPoints; i += 1){
//You also have to change the if (indextogoto == <value>) in the moveGumliMethod;
float opposite = sqrtf( powf(myRadius, 2) - powf(currentDistance, 2) );
currentDistance = i;
switch (j) {
case 1:
xMultiplier = 1;
yMultiplier = 1;
xAdd = currentDistance;
yAdd = opposite;
color = [CCColor blueColor];
break;
case 2:
xMultiplier = 1;
yMultiplier = -1;
xAdd = opposite;
yAdd = currentDistance;
color = [CCColor orangeColor];
break;
case 3:
xMultiplier = -1;
yMultiplier = -1;
xAdd = currentDistance;
yAdd = opposite;
color = [CCColor redColor];
break;
case 4:
xMultiplier = -1;
yMultiplier = 1;
xAdd = opposite;
yAdd = currentDistance;
color = [CCColor purpleColor];
break;
default:
break;
}
int x = (CGFloat)(point.x + xAdd * xMultiplier); //or sprite kit equivalent thereof
int y = (CGFloat)(point.y + yAdd * yMultiplier); //or sprite kit equivalent thereof
CGPoint newPoint = CGPointMake((CGFloat)x,(CGFloat)y); //or sprite kit equivalent thereof
NSValue *pointWrapper = [NSValue valueWithCGPoint:newPoint]; //or sprite kit equivalent thereof
NSLog(#"Point is %#",pointWrapper);
[points addObject:pointWrapper];
}
}
return points;
}
Calculating Nearest Point To Object
-(CGPoint)calculateNearestGumliPoint:(CGPoint)search point { // MY Character is named Gumli
float closestDist = 2000;
CGPoint closestPt = ccp(0,0);
for (NSValue *point in points) {
CGPoint cgPoint = [point CGPointValue];
float dist = sqrt(pow( (cgPoint.x - searchpoint.x), 2) + pow( (cgPoint.y - searchpoint.y), 2));
if (dist < closestDist) {
closestDist = dist;
closestPt = cgPoint;
}
}
return closestPt;
}
I think the best way to make this work is through two SKNode and joint them with SKPhysicsJointPin (Look at the pin example below)
I tried to hang a door sign (SKSpriteNode) on my door(`SkScene), and would like to rotate around on the hanging spot when someone touch it
What I did is making a 1x1 SKNode with a HUGH mass and disabled it's gravity effects.
var doorSignAnchor = SKSpriteNode(color: myUIColor, size: CGSize(width: 1, height: 1))
doorSignAnchor.physicsBody = SKPhysicsBody(rectangleOf: doorSignAnchor.frame.size)
doorSignAnchor.physicsBody!.affectedByGravity = false // MAGIC PART
doorSignAnchor.physicsBody!.mass = 9999999999 // MAGIC PART
var doorSignNode = SKSpriteNode(imageNamed:"doorSign")
doorSignNode.physicsBody = SKPhysicsBody(rectangleOf: doorSignNode.frame.size)
and created a SKPhysicsJointPin to connect them all
let joint = SKPhysicsJointPin.joint(
withBodyA: doorSignAnchor.physicsBody!,
bodyB: doorSignNode.physicsBody!,
anchor: doorSignAnchor.position)
mySkScene.physicsWorld.add(joint)
So it will move like actual door sign, rotate around an arbitrary point (doorSignAnchor)
Reference:
Official document about Sumulating Physics
How to Make Hanging Chains With SpriteKit Physis Joints

Resources