SpriteKit physics giving different results each time - ios

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.

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)];

cocos2d v3: physicsBody of a CCSprite is positioned incorrectly

I'm very new to cocos2d development.
What I am trying to implement is some sprite nodes with physics bodies. As far as I understand, you're supposed to add them to the scene like this ("physics" here is the name of CCPhysicsNode declared earlier):
[physics addChild:node];
instead of
[self addChild:node];
The second one is pretty self-explanatory, I never had any trouble with it. But with the first the position of collision shapes does not match the position of the actual sprite (as seen in debug drawing, and by offset I mean greatly offset, like 2x the actual position). This is how I declare and add the node:
CCSprite *sprite = [CCSprite spriteWithImageNamed:#"sprite.png"];
sprite.position = position;
sprite.physicsBody = [CCPhysicsBody bodyWithRect:[sprite boundingBox] cornerRadius:0];
sprite.physicsBody.collisionType = #"SomeCollisionType";
sprite.name = #"Name";
[physics addChild:sprite];
What am I doing wrong? Please explain me how to make the positions match. TIA.

SpriteKit PhysicsWorld coordinate system, weird runtime joint anchor

Two likely related things here:
1) I can draw a box and add to child from my SKScene impelmentation file with self, self.scene, and myWorld, but not with an SKSprite node's scene property.
SKSpriteNode *bla = [SKSpriteNode spriteNodeWithColor:[UIColor redColor] size:CGSizeMake(100, 100)];
[self.scene addChild:bla]; // If I use bla.scene, it doesn't work. self, self.scene, myworld all do work though.
bla.position = CGPointMake(0, 0);
bla.zPosition = 999;
2) I've seen the related questions here and here, but I'm trying to add a joint during gameplay (grabbing a rope). This method gets called after doing some sorting in `didBeginContact:
-(void)monkey:(SKPhysicsBody *)monkeyPhysicsBody didCollideWithRope:(SKPhysicsBody *)ropePhysicsBody atPoint:(CGPoint)contactPoint
{
if (monkeyPhysicsBody.joints.count == 0) {
// Create a new joint between the player and the rope segment
CGPoint convertedRopePosition = [self.scene convertPoint:ropePhysicsBody.node.position fromNode:ropePhysicsBody.node.parent];
SKPhysicsJointPin *jointPin = [SKPhysicsJointPin jointWithBodyA:playerPhysicsBody bodyB:ropePhysicsBody anchor:convertedRopePosition];
jointPin.upperAngleLimit = M_PI/4;
jointPin.shouldEnableLimits = YES;
[self.scene.physicsWorld addJoint:jointPin];
}
}
I've got showPhyiscs enabled on the scene, so I can see that the joint is ending up in a totally wacky place. Unfortunately, I don't know how to apply the linked solutions since I'm not adding the SKSpriteNodes in this method, they already exist, so I can't just flip the order of position and physicsBody.
I've tried every permutation I could for both of the convertPoint methods. Nothing worked. My best guess is that physicsWorld is using some wacky coordinate system.
Method members of SKPhysicsWorld that relate to position (CGPoint) or frame (CGRect) are to be in scene coordinates. Scene coordinates reference the point {0,0} as the bottom left corner and is consistent throughout SpriteKit.
The scene property of your object named bla will be nil when bla is first created and is set by the scene when added to it.
[bla.scene addChild:bla]; // this won't do anything as scene will be nil when first created
It looks as though convertedRopePosition is being assigned an incorrect value because the second member you're passing into, - (CGPoint)convertPoint:(CGPoint)point fromNode:(SKNode *)node , is the scene when it should be another node in the same node tree as this node. where this node is the caller (in this case the SKScene subclass).
Try replacing the line-
CGPoint convertedRopePosition = [self.scene convertPoint:ropePhysicsBody.node.position fromNode:ropePhysicsBody.node.parent];
with-
CGPoint convertedRopePosition = [self convertPoint:ropePhysicsBody.node.position fromNode:ropePhysicsBody.node];
I came up with a janky work around for this problem. It turns out that the coordinate system offset for physicsWorld was likely due to an anchor difference. Changing the anchors of every related thing made no difference, and you can't change the anchor of the physicsWorld directly, so I ended up adding half of the scene width and half of the scene height to the anchor position of my joint. That got it to show in the right place and behave normally.
This problem persisted once side scrolling was factored in. I've posted other questions here with this same problem but I'll include th
I added the following convenience method to my GameScene.m. It essentially takes the place of the seemingly useless convertTo built in method.
-(CGPoint)convertSceneToFrameCoordinates:(CGPoint)scenePoint
{
CGFloat xDiff = myWorld.position.x - self.position.x;
CGFloat yDiff = myWorld.position.y - self.position.y;
return CGPointMake(scenePoint.x + self.frame.size.width/2 + xDiff, scenePoint.y + self.frame.size.height/2 + yDiff);
}
I use this method to add joints. It handles all of the coordinate system transformations that need to be dealt with that lead to the issue raised in this question. For example, the way I add joints
CGPoint convertedRopePosition = [self convertSceneToFrameCoordinates:ropePhysicsBody.node.position];
SKPhysicsJointPin *jointPin = [SKPhysicsJointPin jointWithBodyA:monkeyPhysicsBody bodyB:ropePhysicsBody anchor:convertedRopePosition];
jointPin.upperAngleLimit = M_PI/4;
jointPin.shouldEnableLimits = YES;
[self.scene.physicsWorld addJoint:jointPin];

SKAction scaleXTo:-1

I am trying to mirror my SKSpriteNode using SKAction *mirrorDirection = [SKAction scaleXTo:-1 y:1 duration:0.0]; but every time I do, the sprite's physics body seems to get messed up and it drops off the screen. I have several objects (floors) beneath the sprite and it falls through all of them as if they are not there.
This only happens when I mirror the sprite. Anyone know how to fix this?
Update:
Reseting the physics body as one answer suggested unfortunately did not fix the problem. It appears that only the contact part of the physics body malfunctions. Gravity still has an effect as the little guy drops like a rock.
I also tried to reset by again self.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:CGSizeMake(self.size.width, self.size.height)];
right after the self.xScale = -1; but this also did not fix the issue.
Add your mirror-able node as a child of some regular SKNode and set the physics body on this parent node instead of the negatively scaled node.
I don't know how to fix this but I would like to suggest one way you could create a mirrored sprite.
To achieve this set the x or y scale of your sprite node to -1. Then use the SKView method textureFromNode: to create a texture from this mirrored node.
You can then use this texture to create a new sprite node that is mirrored but doesn't require any negative scaling.
Don't use an SKAction, just set it directly to the SKSpriteNode's xScale property.
self.yourSprite.xScale = -1;
Roecrew is right about setting the xScale property directly. I would suggest you try this:
node.xScale = -1;
node.physicsBody = node.physicsBody;
You will need to 'reset' the physicsBody each time you change the xScale property.
The xScale issue with physicsBody is a bug in SpriteKit, but I was able to 'retain' the physicsBody using the second line.
I'm meeting the problem exactly like yours. I spent about 2 hours to figure it out.
Just init your physicsBody after you scaleX, i dont know why but i had correct this issue by doing this way.
walkRight = [SKAction sequence:#[resetDirection,[SKAction runBlock:^{
[self changePhysicsDirectionRight];
}],[SKAction repeatActionForever: walk]]];
walkLeft = [SKAction sequence:#[mirrorDirection,[SKAction runBlock:^{
[self changePhysicsDirectionLeft];
}],[SKAction repeatActionForever: walk]]];
walkRight and walkLeft is my action when changing direction, and resetDirection and mirrorDirection is exactly the action i used to scaleXTo:1 and scaleXTo:1
So after i scaleXTo i use a method call changePhysicsDirectionRight to re-init my physicBody like
- (void)changePhysicsDirectionRight{
self.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:CGSizeMake(self.size.width,self.size.height)];
self.physicsBody.categoryBitMask = guyCategory;
self.physicsBody.contactTestBitMask = 0;
self.physicsBody.collisionBitMask = 0;
}
Remember to re-assign all your category and everything like you init before.
I hope someone pro at spritekit can tell me the reason why ....

Collision detection in Sprite Kit

I'm using collision detection in Sprite Kit. It is working and preventing my sprites from crossing paths. However, I'm not getting notifications in didBeginContact: and I don't seem to have any control over how the physics engine responds when a collision occurs.
I have various cars (SKSpriteNodes) moving around following paths using the SKAction followPath:asOffset:orientToPath:duration:
Previously, if two cars crossed paths they would both just continue as normal with one driving over the other. To implement collision detection I have made the following changes...
Added this to my #interface:
<SKPhysicsContactDelegate>
Added this to my #implementation:
static const uint32_t carCategory = 0x1 << 0;
Added this in my init method:
self.physicsWorld.contactDelegate = self;
self.physicsWorld.gravity = CGVectorMake(0.0, 0.0);
I create my blue car:
- (void)addBlueCar
{
_blueCar = [SKSpriteNode spriteNodeWithImageNamed:#"Blue Car.png"];
_blueCar.position = CGPointMake(818.0, -50.0);
_blueCar.name = #"car";
[self addChild:_blueCar];
CGSize contactSize = CGSizeMake(_blueCar.size.width - 5.0, _blueCar.size.height - 5.0);
_blueCar.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:contactSize];
_blueCar.physicsBody.categoryBitMask = carCategory;
_blueCar.physicsBody.collisionBitMask = carCategory;
_blueCar.physicsBody.contactTestBitMask = carCategory;
}
And I also create a red car:
- (void)addRedCar
{
_redCar = [SKSpriteNode spriteNodeWithImageNamed:#"Red Car.png"];
_redCar = CGPointMake(818.0, -50.0);
_redCar = #"car";
[self addChild: _redCar];
CGSize contactSize = CGSizeMake(_blueCar.size.width - 5.0, _blueCar.size.height - 5.0);
_redCar.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:contactSize];
_redCar.physicsBody.categoryBitMask = carCategory;
_redCar.physicsBody.collisionBitMask = carCategory;
_redCar.physicsBody.contactTestBitMask = carCategory;
}
There are other cars as well, but I'm just using these two until I figure out what the problem is. I want to be notified of any collision between any two cars. That's why I'm just using a single category, carCategory.
The first problem is that I get no notifications. I have this yet it never logs anything to the console:
- (void)didBeginContact:(SKPhysicsContact *)contact
{
NSLog(#"Contact");
}
The second problem is that when two cars cross paths they start nudging each other off course. This is ok, but I don't seem to able to manage any aspects of how the collision is handled. The bizarre thing is that nothing changes when I do this to both of my car creation methods:
- (void)addBlueCar
{
_blueCar = [SKSpriteNode spriteNodeWithImageNamed:#"Blue Car.png"];
_blueCar.position = CGPointMake(818.0, -50.0);
_blueCar.name = #"car";
[self addChild:_blueCar];
CGSize contactSize = CGSizeMake(_blueCar.size.width - 5.0, _blueCar.size.height - 5.0);
_blueCar.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:contactSize];
// _blueCar.physicsBody.categoryBitMask = carCategory;
// _blueCar.physicsBody.collisionBitMask = carCategory;
// _blueCar.physicsBody.contactTestBitMask = carCategory;
}
Collision detection still works the same with those three lines commented out for both cars. This just doesn't seem right to me. Collision detection only ceases when I also comment out this line:
_blueCar.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:contactSize];
So my main question is: why is the contactTest not reporting anything? My second question is: why is collision detection happening when I don't assign a categoryBitMask, collisionBitMask or contactTestBitMask to any car?
collisionBitMask's default value is 0xFFFFFFFF (all bits set). Therefore the node will collide with each physicBody on the scene.
Just a small observation, perhaps you should setup your nodes and set the contact delegate in the scene's didMoveToView method.
If that doesn't solve it, any chance you can post some working code to github for debugging?
Something that helped me with this: Collisions and contacts are different things.
Collision is handled by the physics engine and setting collisionBitMask will determine which bodies collide and interact with each other in the physics world. By default, all your bodies will collide with each other, which kinda makes sense.
Contacts are events- when bodies make contact, will they create a notification that you can use in the delegate method to do more interesting things. If you have a ball you want to hit with a paddle, you want them to collide at a minimum. But if you have a ball moving through a cloud, you probably don't want them to collide in the physics world, but you might want to register the contact.

Resources