for some reason when i apply a physics body around the skshapenode it places the physics body more to the left and downwards of the actual shape even though the parameters of the physics body comes from the shape. does anyone know why? thanks.
-(SKShapeNode*) createGround1
{
//create a rectangle
ground1 = [SKShapeNode shapeNodeWithRect:CGRectMake(0, 0, 300, 19)];
ground1.position = CGPointMake(800, 500);
//applies physics to rectangle
ground1.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize: CGSizeMake(ground1.frame.size.width, ground1.frame.size.height)];
ground1.physicsBody.allowsRotation = false;
ground1.physicsBody.dynamic = false;
ground1.physicsBody.restitution = 0.0;
return ground1;
}
This happens because the anchor points are different. You need to define a center point for your physics body like this:
ground1.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:CGSizeMake(ground1.frame.size.width, ground1.frame.size.height) center:CGPointMake(150, 9.5)];
Related
I am trying to create a complex SKPhysicsBody that does not bounce on the top, but does allow bouncing on the sides and bottom. Currently I am creating two nodes. One that has the image of the Sprite and a no restitution PhysicsBody on the top.
My second node matches the first node, but is clear with the same size as the first node It just has a PhysicsBody on the front and bottom and has a restitution. to allow my main character to bounce off the bottom and front.
Here is the code for my current setup:
//This is the top part.
- (void)nodePhysicsBodySetup:(SKSpriteNode *)node
{
CGPoint topStart = CGPointMake(0, node.size.height);
CGPoint topEnd = CGPointMake(node.size.width, node.size.height);
SKPhysicsBody *topEdge = [SKPhysicsBody bodyWithEdgeFromPoint:topStart toPoint:topEnd];
node.physicsBody = topEdge;
//[SKPhysicsBody bodyWithBodies:#[topEdge, frontEdge, bottomEdge]]
//node.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:node.frame.size];
node.physicsBody.dynamic = NO;
node.physicsBody.restitution = 0.0;
node.physicsBody.affectedByGravity = NO;
node.physicsBody.categoryBitMask = groundCategory;
node.physicsBody.collisionBitMask = mainHeroCategory;
node.physicsBody.contactTestBitMask = mainHeroCategory;
}
//This is the bottom physicsbody that matches up with the image of the main node.
- (void)bottomNodePhysicsBodySetup:(SKSpriteNode *)node
{
CGPoint topStart = CGPointMake(0, node.size.height);
CGPoint frontEnd = CGPointMake(0,0);
CGPoint bottomEnd = CGPointMake(node.size.width,0);
SKPhysicsBody *frontEdge = [SKPhysicsBody bodyWithEdgeFromPoint:topStart toPoint:frontEnd];
SKPhysicsBody *bottomEdge = [SKPhysicsBody bodyWithEdgeFromPoint:frontEnd toPoint:bottomEnd];
bottomNode.physicsBody = [SKPhysicsBody bodyWithBodies:#[frontEdge, bottomEdge]];
bottomNode.physicsBody.dynamic = NO;
bottomNode.physicsBody.affectedByGravity = NO;
bottomNode.physicsBody.restitution = 0.5;
bottomNode.physicsBody.categoryBitMask = otherCategory;
bottomNode.physicsBody.collisionBitMask = mainHeroCategory;
bottomNode.physicsBody.contactTestBitMask = mainHeroCategory;
}
Picture as screenshots are under NDA:
Currently, the Green PhysicsBody is attached to the node, and the red PhysicsBody is attached to a transparent second node with the same position as the first node.
This does not really work. My main character (mainHeroCategory) gets stuck sometimes on the node corner where the PhysicsBody with restitution and the one without meet. What is a better way to do this?
I would try to do this using only one physics body and look at the contact delegate methods to determine where the contact occurred and what the normal vector is (that information is in the SKPhysicsContact object passed in the delegate method).
Once I got that information I would apply an impulse to the mainHeroCategory object, or not, depending on where the contact occurred.
I suggest you join the physics bodies with an SKPhysicsJointFixed or by constraining the bodies with an SKConstraint or two. SpriteKit only uses the shape when merging two or more bodies with bodyWithBodies. From the docs...
The properties on the children, such as mass or friction, are ignored.
Only the shapes of the child bodies are used.
The restitution property is ignored.
It's possible for one node to have two physics body paths? I want to create a node that has two (circle) physics bodies on the sides of the node.
If it's not possible, is there are any workaround to achieve that? thank you
You want to use [SKPhysicsBody bodyWithBodies:...]. From the docs :
The shapes of the physics bodies passed into this method are used to
create a new physics body whose covered area is the union of the areas
of its children. These areas do not need to be contiguous. If there is
space between two parts, other bodies may be able to pass between
these parts. However, the physics body is treated as a single
connected body, meaning that a force or impulse applied to the body
affects all of the pieces as if they were held together with an
indestructible frame.
It would look something like this :
SKPhysicsBody *leftCircle = [SKPhysicsBody bodyWithCircleOfRadius:leftCircleRadius center:leftCircleCenter];
SKPhysicsBody *rightCircle = [SKPhysicsBody bodyWithCircleOfRadius:rightCircleRadius center:rightCircleCenter];
node.physicsBody = [SKPhysicsBody bodyWithBodies:#[leftCircle, rightCircle]];
Here's an example of how to connect to sprite nodes using SKPhysicsJointFixed. First, create two sprites:
SKSpriteNode *sprite1 = [SKSpriteNode spriteNodeWithColor:[SKColor blueColor] size:CGSizeMake(64, 64)];
// position must be set before creating physics body to avoid bug in iOS 7.0.x
sprite1.position = CGPointMake(CGRectGetMidX(self.frame),
CGRectGetMidY(self.frame));
sprite1.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:sprite1.size];
sprite1.physicsBody.restitution = 1.0;
[self addChild:sprite1];
SKSpriteNode *sprite2 = [SKSpriteNode spriteNodeWithColor:[SKColor blueColor] size:CGSizeMake(64, 64)];
sprite2.position = CGPointMake(CGRectGetMidX(self.frame)-sprite2.size.width*2,
CGRectGetMidY(self.frame));
sprite2.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:sprite2.size];
sprite2.physicsBody.restitution = 1.0;
[self addChild:sprite2];
then connect the nodes by calling this method:
[self connectNode1:sprite1 toNode2:sprite2];
This method joins two nodes at their midpoint. Note that both physic bodies must be in the scene prior to calling this method.
- (void) connectNode1:(SKSpriteNode *)node1 toNode2:(SKSpriteNode *)node2
{
CGPoint midPoint = CGPointMake((node1.position.x + node2.position.x)/2,
(node1.position.y + node2.position.y)/2);
SKPhysicsJointFixed *joint = [SKPhysicsJointFixed jointWithBodyA:node1.physicsBody
bodyB:node2.physicsBody
anchor:midPoint];
[self.physicsWorld addJoint:joint];
}
Here is an easy way to achieve the behavior you are looking for:
How to detect contact on different areas of a physicsbody
//Create SpriteNode1 & physicsBody
//Create SpriteNode2 & physicsBody
[SpriteNode1 addChild: SpriteNode2]
You can position SpriteNode2 relative to SpriteNode1. Any movement, etc. performed on SpriteNode1 will also move SpriteNode2.
Set: SpriteNode2.PhysicsBody.Dynamic=NO;
You could also create a SpriteNode that acts as the main object and add both SN1 and SN2 as children if you find that easier.
Whenever i try to draw a SKSpriteNode, it would be drawn lower than it should be.
But it seems that other SKSpriteNode works fine with no problems.
This is my current code:
func initMainGround() {
let gSize = CGSizeMake(self.size.width/4*3, 120);
ground = SKSpriteNode(color: SKColor.brownColor(), size: gSize);
ground.name = gName;
ground.position = CGPointMake(0, 0);
ground.physicsBody = SKPhysicsBody(rectangleOfSize: ground.size);
ground.physicsBody.restitution = 0.0;
ground.physicsBody.friction = 0.0;
ground.physicsBody.angularDamping = 0.0;
ground.physicsBody.linearDamping = 0.0;
ground.physicsBody.allowsRotation = false;
ground.physicsBody.usesPreciseCollisionDetection = true; //accurate collision
ground.physicsBody.affectedByGravity = false;
ground.physicsBody.dynamic = false;
ground.physicsBody.categoryBitMask = gBitmask;
ground.physicsBody.collisionBitMask = pBitmask;
self.addChild(ground);
}
func addBomb() {
let bomb = SKSpriteNode(imageNamed: "trap");
bomb.size = CGSizeMake(30, 30);
bomb.position = CGPointMake(ground.position.x, actualY+10);
bomb.physicsBody = SKPhysicsBody(circleOfRadius: bomb.size.width/2);
bomb.physicsBody.restitution = 0.0;
bomb.physicsBody.allowsRotation = false;
bomb.physicsBody.usesPreciseCollisionDetection = true;
bomb.physicsBody.affectedByGravity = false;
bomb.physicsBody.dynamic = false;
bomb.physicsBody.categoryBitMask = bBitmask;
bomb.physicsBody.collisionBitMask = pBitmask;
bomb.physicsBody.contactTestBitMask = pBitmask;
self.addChild(bomb);
}
Although the bomb is suppose to be almost directly above the ground, but it seems that the bomb is almost 100+ above the ground instead.
The ground is suppose to fill up almost one third of the screen height since the game is in landscape, but it is way lower than normal.
Why is it that the ground is drawn at the wrong position, but the bomb is drawn at the correct position?
This sounds like the same problem I had when starting to work with SpriteKit. If your game uses an .sks file to present it's main scene (as it does by default), this scene uses arbitrary dimension values defined in the .sks file.
Try setting the dimensions of your scene dynamically to see if this is the case.
In your didMoveToView function, add something like this at the top of the function:
self.size = view.bounds.size
This way the dimension values from the .sks file will be overridden with your actual screen dimensions.
Hope this helps!
Set the anchorPoint on your ground image.
ground.anchorPoint = CGPointZero // same as CGPointMake(0, 0)
ground.position = CGPointZero
The default anchorPoint is (0.5, 0.5), the center of the image. So without it set, the center of the ground image is drawn in the lower left corner of the screen (0, 0).
Your bomb draws where you expect it for the same reason, the center is placed at the position you specified.
I'm trying to create a player node on my scene. But the image "player.png" doesn't fit the physics body. They are both separate.
PhysicsBody is in the ground and the image is floating on the air. What's wrong with my code? How do I make them both together?
#import "Player.h"
#implementation Player
- (instancetype)init {
self = [super initWithImageNamed:#"player.png"];
self.name = #"player";
self.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:CGSizeMake(50, 50)];
self.physicsBody.dynamic = YES;
self.physicsBody.allowsRotation = NO;
self.physicsBody.affectedByGravity = YES;
self.zPosition = 100;
self.anchorPoint = CGPointMake(0.5, 0);
return self;
}
#end
// myScene.h
-(void)createSceneContents {
self.currentBackground = [Background generateBackground];
[self addChild: self.currentBackground];
self.physicsWorld.gravity = CGVectorMake(0, _gravity);
self.physicsWorld.contactDelegate = self;
Player *player = [[Player alloc]init];
player.position = CGPointMake([UIScreen mainScreen].applicationFrame.size.width/2, 50);
[self addChild:player];
}
Anchor point is the center of your physics body also here.
Your point 0.5, 0.0 means center x and zero y.
So, the center of your physics body is at the bottom edge of your sprite node.
Likely the bottom center of the image.
But the physics body extends downward from there.
This is because of the method you used to create the physics body.
Anchor points are confusing.
They play dual roles sometimes.
They include a lot of poorly documented implicit behaviors.
Unless you have logic relying on the anchor point, it's best to shy away from changing them.
With a physics body, what matters is where the body is in the physics world.
Keep your model of the sprite as simple as possible.
Refine constantly towards the simplest model to get the job done. It will simplify your game logic.
I implemented a basic box2d project in iOS with a bouncing ball, but the ball seems to bounce before it actually reaches the ground. On each progressive bounce it seems to get closer to the bottom and eventually rests on the ground. I took a video of what happens: http://f.cl.ly/items/1S06373Z1l2w1z243E0k/Bounce.m4v
Here is where I set everything up:
CGSize screenSize = self.view.bounds.size;
// Define the gravity vector.
b2Vec2 gravity;
gravity.Set(0.0f, -9.81f);
bool doSleep = true;
// Construct a world object, which will hold and simulate the rigid bodies.
world = new b2World(gravity);
world->SetAllowSleeping(doSleep);
world->SetContinuousPhysics(true);
b2BodyDef groundBodyDef;
groundBodyDef.position.Set(0,0);
b2Body *groundBody = world->CreateBody(&groundBodyDef);
b2EdgeShape groundEdge;
b2FixtureDef boxShapeDef;
boxShapeDef.shape = &groundEdge;
//bottom
groundEdge.Set(b2Vec2(0,0), b2Vec2(screenSize.width/PTM_RATIO, 0));
groundBody->CreateFixture(&boxShapeDef);
// left wall
groundEdge.Set(b2Vec2(0,0), b2Vec2(0,screenSize.height/PTM_RATIO));
groundBody->CreateFixture(&boxShapeDef);
// top
groundEdge.Set(b2Vec2(0, screenSize.height/PTM_RATIO),
b2Vec2(screenSize.width/PTM_RATIO, screenSize.height/PTM_RATIO));
groundBody->CreateFixture(&boxShapeDef);
// right wal
groundEdge.Set(b2Vec2(screenSize.width/PTM_RATIO, screenSize.height/PTM_RATIO),
b2Vec2(screenSize.width/PTM_RATIO, 0));
groundBody->CreateFixture(&boxShapeDef);
//ball
b2BodyDef ballBodyDef;
ballBodyDef.type = b2_dynamicBody;
ballBodyDef.position.Set(self.ball.position.x/PTM_RATIO, self.ball.position.y/PTM_RATIO);
ballBodyDef.userData = (__bridge void *)_ball;
body = world->CreateBody(&ballBodyDef);
b2CircleShape circle;
circle.m_radius = 60.0/PTM_RATIO;
b2FixtureDef ballShapeDef;
ballShapeDef.shape = &circle;
ballShapeDef.density = 0.8f;
ballShapeDef.friction = 0.0f;
ballShapeDef.restitution = 0.8f;
body->CreateFixture(&ballShapeDef);
and then this is what I run every 1/60th of a second to update the position
int32 velocityIterations = 8;
int32 positionIterations = 1;
world->Step(1.0f/60.0f, velocityIterations, positionIterations);
CGPoint newCenter = CGPointMake(body->GetPosition().x * PTM_RATIO,
self.view.bounds.size.height - body->GetPosition().y * PTM_RATIO);
self.ball.position = newCenter;
If you declare the ball body as dynamic
b2BodyDef ballBodyDef;
ballBodyDef.type = b2_dynamicBody;
// finish creating...
And initialize the world with some gravity value (which you've done), you shouldn't have to manually set the position of the ball body at all. Without seeing all of your code, my hunch is that you have declared the ball as dynamic already, and your manual position updates are conflicting with box2d's gravity. The video seems to show the ball being pulled down by gravity while having its position artificially shifted. I recommend removing the line assigning to self.ball.position entirely - directly setting a physics body's position like this is generally not a good idea.
I found out what was the issue. The ball on the screen is a CALayer and when changing its position it was using implicit animations. It now acts fine when setting the position this way
[CATransaction begin];
[CATransaction setDisableActions:YES];
CGPoint newCenter = CGPointMake(body->GetPosition().x * PTM_RATIO,
self.view.bounds.size.height - body->GetPosition().y * PTM_RATIO);
self.ball.position = newCenter;
[CATransaction commit];