I have a SpriteNode which is affected by gravity only on the y-Axis and can be moved with the accelerometer on the x-Axis. I also have a border (SKPhysicsBody) on my Scene which keeps my node inside my scene. The problem is now that my node ignores the border when it performs its SKAction caused by the accelerometer.
My code for the x movement/ accelerometer and its action:
birdNode is my SpriteNode
float destX = 0.0;
float currentX = birdNode.position.x;
BOOL shouldMove = NO;
if (data.acceleration.x < -0.1) {
destX = currentX + (data.acceleration.x * playerXSpeed);
shouldMove = YES;
}
else if (data.acceleration.x > 0.1) {
destX = currentX + (data.acceleration.x * playerXSpeed);
shouldMove = YES;
}
if (shouldMove) {
if (birdNode.position.x + destX < self.frame.size.width || birdNode.position.x - destX > 0) {
SKAction *moveBird = [SKAction moveToX:destX duration:0.1];
[birdNode runAction:moveBird];
}
}
I assume that you have your collision bit masks properly set up to handle collisions. You should confirm this with a test to double check.
I believe your issue is with the speed of your object. If the object is moving too fast, and I do not see any velocity limits in your posted code, your object can move past the screen boundary in one update cycle.
For example, your current object's position is (400,100). Given enough velocity, your object's next update: position could be (600,100). This means that your object literally jumped past the boundary without causing a collision.
The solution is to either limit the velocity or to institute a position check before setting your object's new position. For example, if the new x position is > the screen width then set the x position to the max allowed screen width x.
Related
I am trying to use applyImpulse to make a sprite jump to a specific height. In the example code below, the dynamic black circle jumps to the same height as the static red circle, but it only works with a kludge.
If my physics is right, the required initial vertical momentum to launch a projectile to height Y is given by mass * sqrt(2 * gravity * Y). Yet this formula results in the black circle moving very little.
Through trial and error, I have discovered that I can make the red circle jump more or less accurately by multiplying the vertical component of the impulse vector by 12.3, as illustrated in the code below.
This seems completely arbitrary and is driving me crazy. I am obviously doing something wrong. What's the right way to do this?
Here's what I think is the relevant bit of code:
let dy = mass * sqrt(
2 * -self.physicsWorld.gravity.dy
* (fixedCircle.position.y-jumpingCircle.position.y))
* 12.3
jumpingCircle.physicsBody?.applyImpulse(CGVectorMake(0, CGFloat(dy)))
Here's the GameScene.swift class in its entirety, should you wish to copy and paste...
import SpriteKit
class GameScene: SKScene {
var jumpingCircle = SKShapeNode()
var fixedCircle = SKShapeNode()
override func didMoveToView(view: SKView) {
self.scaleMode = .AspectFit
// Create an exterior physical boundary
self.physicsBody = SKPhysicsBody(edgeLoopFromRect:self.frame)
self.physicsWorld.gravity = CGVectorMake(0, -5);
// Create the jumping circle
jumpingCircle = SKShapeNode(circleOfRadius: 50)
jumpingCircle.position = CGPoint(x: 100,y: 0)
jumpingCircle.strokeColor = .blackColor()
jumpingCircle.physicsBody = SKPhysicsBody(circleOfRadius: 50)
jumpingCircle.physicsBody?.linearDamping = 0.0
self.addChild(jumpingCircle)
// Create the fixed circle
fixedCircle = SKShapeNode(circleOfRadius: 50)
fixedCircle.position = CGPoint(x: 100,y: 384)
fixedCircle.strokeColor = .redColor()
self.addChild(fixedCircle)
}
override func touchesEnded(touches: Set<NSObject>, withEvent event: UIEvent) {
// to reach certain height, initial y velocity should by sqrt(2 * gravity * Y)
// momentum required to reach this velocity is mass * velocity
// therefore, vertical impulse should be given by mass * sqrt(2 * gravity * Y)
if let mass = jumpingCircle.physicsBody?.mass{
// calculate vertical impulse
// this formula "should work" but does not
// let dy = mass * sqrt(2 * -self.physicsWorld.gravity.dy * (fixedCircle.position.y-jumpingCircle.position.y))
// this formula "works", but has the arbitrary multiplier
let dy = mass * sqrt(2 * -self.physicsWorld.gravity.dy * (fixedCircle.position.y-jumpingCircle.position.y)) * 12.3
// apply the Impulse
jumpingCircle.physicsBody?.applyImpulse(CGVectorMake(0, CGFloat(dy)))
}
}
}
Figured it out.
SpriteKit apparently has an undocumented pixel-to-"meter" ratio of 150.
I discovered this when I realized the mass SpriteKit was automatically calculating for my circles was not 50*pi*r^2 as it should be. I worked backward from mass to calculate the radius SpriteKit was using, and it was 7500, which happens to be 50*150.
And 12.3? It just happens to be (approximately) the square root of 150.
So to make these physics simulations work, you have to consider this ratio. I'm calling it "pixel to unit" (PTU) because it has nothing to do with meters in spite of Apple's insistence that SpriteKit uses SI units. But because it's undocumented it seems possible to change, so I'm kicking off my simulation using the following line of code to determine the true PTU:
let ptu = 1.0 / sqrt(SKPhysicsBody(rectangleOfSize: CGSize(width:1,height:1)).mass)
This is an expensive operation, so it should only be called once. I'm calling it when setting up the initial Scene.
My impulse calculation is now as follows:
let dy = mass * sqrt(2
* -self.physicsWorld.gravity.dy
* (fixedCircle.position.y-jumpingCircle.position.y
* ptu))
And now the black circle jumps to perfect alignment with the red circle and I can go back to sleeping at night.
Instead of applyImpulse use velocity. Velocity will be more specific.
jumpingCircle.physicsBody?.velocity = CGVectorMake(jumpingCircle.physicsBody.velocity.dx, 300.0f);
Code not tested.
I want to move a physicsBody with the applyImpulse method in a direction based on the physicsBody rotation.
Foe example, the physicsBody is a square in shape, I call a "move" which will apply an impulse to make it move up vertically. I then call a method to rotate the physicsBody 45 degrees right. If I call the "move" method again, the physicsBody will move diagonally right and up.
I suggest that you follow Sprite Kit’s coordinate and rotation conventions. Specifically, your sprite image should be facing right at zero degrees (the default value), and a positive value is a counter-clockwise rotation. That said, here's one way to apply an impulse in the direction a sprite is facing:
// Specify the force to apply to the SKPhysicsBody
CGFloat r = 5;
// Create a vector in the direction the sprite is facing
CGFloat dx = r * cos (sprite.zRotation);
CGFloat dy = r * sin (sprite.zRotation);
// Apply impulse to physics body
[sprite.physicsBody applyImpulse:CGVectorMake(dx,dy)];
UPDATED:
Fixed with the below thanks to #0x141E
-(void)characterJump {
CGFloat radianFactor = 0.0174532925;
CGFloat rotationInDegrees = _body.zRotation / radianFactor;
CGFloat newRotationDegrees = rotationInDegrees + 90;
CGFloat newRotationRadians = newRotationDegrees * radianFactor;
CGFloat r = 500;
CGFloat dx = r * cos(newRotationRadians);
CGFloat dy = r * sin(newRotationRadians);
[_body.physicsBody applyImpulse:CGVectorMake(dx, dy)];
}
I'm a beginner at programming, and I've been trying to make an object orbit around another object (or just move in a circle). But I haven't succeeded very well. Any ideas?
You need some constants to specify radius and speed:
const float speed = 100.0f;
const float radius = 50.0f;
you also need some variable to store angle:
float angle;
- (void)updateObject:(NSTimeInterval)dt
{
angle += speed * dt;
angle = fmodf(angle, 360.0f);
float x = cosf(DEGREES_TO_RADIANS(angle)) * radius;
float y = sinf(DEGREES_TO_RADIANS(angle)) * radius;
float newXPosition = _yourSprite.position.x + x;
float newYPosition = _yourSprite.position.y + y;
//Assign the values to your sprite
_yourSprite.position = ...
}
Try connecting two nodes with SKPhysicsJointLimit, with the first node not movable (maybe not dynamic), set the linear damping of the second node to zero and disable gravitation forces on it. It also should not collide with any other object, of course.When the joint is stretched to its maximum and you apply an Impulse vertical to the connection between the two objects, the object should start orbiting around the other one.
I have not tested this one.
Im wondering at my setting ball.position function, the point is to set ball's point to other spritenode(in left/middle/right side), i tried to write my own function, but it was full of bugs, (it's commented), next after review developer lib's. I found anchorPoint to parent position, but it's not working, i dont know exactly how to set current parent for ball. I'll be really grateful for some advices.
if (ball.position.y < block.position.y +10 && ball.position.y > block.position.y -10) {
midX = CGRectGetMidX(self.frame);
side = block1.size.width/2;
if (emitter.position.x < midX - side+5) {
// fixedEmitterPos = emitter.position.x+side;
ball.anchorPoint = CGPointMake(0,0.5);
}
if (emitter.position.x > midX + side-5){
// fixedEmitterPos = emitter.position.x-side;
ball.anchorPoint = CGPointMake(1,0.5);
}
if (emitter.position.x == 160) {
// fixedEmitterPos = emitter.position.x;
ball.anchorPoint = CGPointMake(0.5,0.5);
}
}
I don't know what exactly is your question but to change your anchor point you don't change anything in parent node. For example if anchor point is
ball.anchorPoint = CGPointMake(0.5,0.5);
the anchor point in exactly in the middle and if you change position of the ball like:
ball.position == CGPointMake(50,50);
the ball center will be exactly in that point (50, 50).
but if the anchor point will be:
ball.anchorPoint = CGPointMake(1, 1);
and you change the position to :
ball.position == CGPointMake(50,50);
the ball centre will be in
X = 50 - (ball width / 2)
Y = 50 - (ball height / 2)
If you want to set up ball position base on other sprite node you can do something like that:
//Attach left centre side of ball to other sprite:
ball.anchorPoint = CGPointMake(0, 0.5);
ball.position.x == otherSprit.position.x + otherSprit.size.with;
ball.position.y == otherSprit.position.y;
//Attach right centre side of ball to other sprite:
ball.anchorPoint = CGPointMake(1, 0.5);
ball.position.x == otherSprit.position.x;
ball.position.y == otherSprit.position.y;
Hope this is what you are about.
During game play, I would like to be able to change the ground edges in a Box2D world. I have created a ground body and then I am adding a ground fixture to the body. For example, the following code will create a flat ground 20 pixels above the bottom of the screen in my Box2D world:
b2BodyDef groundBodyDef;
groundBodyDef.type = b2_staticBody;
groundBodyDef.position.Set(0, 0);
groundBody = world->CreateBody(&groundBodyDef);
b2PolygonShape groundShape;
b2FixtureDef groundFixtureDef;
groundFixtureDef.shape = &groundShape;
groundFixtureDef.density = 0.0;
CGSize screenSize = [CCDirector sharedDirector].winSize;
int num = 2;
b2Vec2 verts[] = {
b2Vec2(-screenSize.width / 100.0, 20.0 / 100.0),
b2Vec2(screenSize.width / 100.0, 20.0 / 100.0)
};
for(int i = 0; i < num - 1; ++i) {
b2Vec2 offset = b2Vec2(screenSize.width/2/PTM_RATIO,
20.0/2/PTM_RATIO);
b2Vec2 left = verts[i] + offset;
b2Vec2 right = verts[i+1] + offset;
groundShape.SetAsEdge(left, right);
groundBody->CreateFixture(&groundFixtureDef);
}
Suppose I need change the verts array to have a different ground fixture definition? For example, is it possible to dynamically raise the ground by 50 pixels between moves of a player? Do I need to delete the entire ground body and recreate the ground body and fixture, or can I just delete or modify the existing ground fixture?
I found that it worked to destroy the current groundBody followed by the creation of a new groundBody and ground fixture works fine. I don't know if this is best for performance, but it works like I wanted it to.
So basically, at the appropriate time during my game, I do the following:
world->DestroyBody(groundBody);
Then I re-execute the code I showed in the problem statement above, substituting the 20.0 pixel value with the 50.0 pixel value in the verts array as follows:
b2Vec2 verts[] = {
b2Vec2(-screenSize.width / 100.0, 50.0 / 100.0),
b2Vec2(screenSize.width / 100.0, 50.0 / 100.0)
}
Change b2PolygonShape to b2EdgeShape.
CGSize s = [[CCDirector sharedDirector] winSize];
b2EdgeShape groundBox;
// bottom
groundBox.Set(b2Vec2(0.0f,0.0f), b2Vec2(s.width/PTM_RATIO,0.0f));
groundBody->CreateFixture(&groundBox,0);
You can't change fixture definition at runtime. So use different fixture and switch that body at run time.