SpriteKit Pinball Flipper Behavior - ios

I'm trying to build a pinball game with spritekit. The ball is affected by gravity and reacts properly, but I can't figure out a way to move the flippers so that they affect the ball's physics. Right now I'm rotating the flippers around an anchor with an SKAction like so
//set anchor point to left edge for rotation
theLeftFlipper.anchorPoint = CGPointMake(0,0.5);
//rotate (done in touchesBegan)
SKAction *rotateLeft = [SKAction rotateByAngle: -1.5f duration:0.1];
[leftFlipper runAction:rotateLeft]
I'm using this to toggle the flipper as up or down around a pivot on its edge, as would be expected with a normal pinball machine's flippers. There are two problems with this. First, the physicsBody of the flipper isn't actually moved to match up with the new anchor of the flipper, so the physics body isn't in the same location as the rendered flipper. Second, when rotating with an action the ball's position is affected but not it's velocity so hitting the ball with the flipper doesn't shoot it upwwards. Is there a better way to achieve this?

Based on the little bit of code you posted, I suggest the following:
If you haven't already, add skView.showsPhysics = YES; in your project's View Controller viewDidLoad. This will allow you to see the physics bodies of your objects.
If the physics body for your flipper is not right, then use CGMutablePathRef to create a custom physics body or you can use this utility.
Make sure you have collisions setup correctly for your flipper and the ball.
Set the ball's and flipper's physics body restitution property to a higher level. This will create the more of a bounce when the two object collide.

Related

How to reduce the impact of gravity on a single SKSpriteNode

I am building an endless runner game where platforms appear at random intervals and then scroll right to left across the screen.
I have a sprite that jumps up vertically using
- (void)makeCharacterJump {
[self.spriteCaveman.physicsBody applyImpulse:CGVectorMake(0.0f, 80.0f)];
}
The problem is that the effect of gravity on the sprite means that it falls quite quickly and cant make the gap between the platforms.
What I would like to do is slightly slow down the effect of gravity on the falling sprite so it creates the impression of slightly floating down.
Any ideas?
If the character is the only node affected by gravity then you can change the scene’s gravity with:
self.physicsWorld.gravity = CGVectorMake(0, desiredGravity);
If it is not then you’ll have to play with the character’s physics body properties: friction, linearDamping or angularDamping values.
Hope that helps.

Can I move a node across the screen without SKAction?

I am developing the iPhone version of an Android game using SpriteKit and objective-c. The Android version was developed using libGDX. There the movement across the screen was done by updating the coordinate values whenever the predefined render method is called. While reading the docs in SpriteKit, I found that here the nodes are moved across the screen by using SKAction where the end coordinates and the duration is given and the action is applied to the node. Is this the only way the node can be moved? Or can I update the coordinates(in the update method according to the time interval) even after the adding the node to the parent node(without calling the SKAction anywhere)?
There are a couple of ways to make a node move.
Changing the node's coordinates myNode.position = CGPointMake(myNode.position.x+1, myNode.position.y); will move the node to the right. This method works with regardless on whether they have a physics body or not.
Apply velocity to the node's physics body myNode.physicsBody.velocity = CGVectorMake(100, myNode.physicsBody.velocity.dy); moves the node to the right. Your node requires a physics body for this to work.
Apply an impulse [myNode.physicsBody applyImpulse:CGVectorMake(100, myNode.physicsBody.velocity.dy)]; moves the node to the right. Your node requires a physics body for this to work.
Apply a force [myNode.physicsBody applyForce:CGVectorMake(100, myNode.physicsBody.velocity.dy)]; moves the node to the right. Your node requires a physics body for this to work.
When you apply force is like a motor pushing all the time. If you apply impulse it's like kicking a ball once.
Read up on each method in more detail in the SKPhysicsBody reference.
You can move a node by specifying the position property (see the SKNode
documentation)
node.position = CGPointMake(x, y);

Make a SCNNode drop using gravity?

I have a SceneKit setup and have one Sphere in it that is setup as a Dynamic body.
I am able to run the app and see the sphere drop on the static body floor.
What I am trying to do is setup the scene so the sfere initially does not drop.
Then when a function is run I want the sfere to drop.
what is the correct logic / steps to make the scene suddenly (maybe when a button is pressed or something) drop the sphere?
I have tried also to set the sphere to mass 0 and then set the mass to 100 but it does not cause the drop...
Mass doesn't control how fast something falls. This is true in the real world, but more so in simulations that take shortcuts instead of simulating every detail of real-world physics. (Sadly, iOS devices still don't have the CPU power to account for the rotating reference frame of the Earth, the Van der Waals attraction between your sphere and any body especially close to it, the strong force that keeps its triangles atoms together, etc etc.) In SceneKit, gravity is just a constant acceleration in a specific direction.
Setting mass to zero and switching it to something else interferes with the distinction between static/kinematic and dynamic bodies... so don't do that.
As #mnuages notes, you can add/remove the physics body from your sphere when you want it to be affected or unaffected by physics entirely.
But what if you want to keep the sphere's physics body for other reasons—such as allowing other bodies to collide with it even before you make it start falling? There are a few approaches you could use for that:
Set the sphere body's damping to 1.0.
Set the sphere body's velocityFactor to zero (at least in the direction of gravity).
Both of those will keep the sphere from moving when something else hits it. If you want the ball to get knocked around, but not be affected by gravity, the best thing to do might be to switch out scene gravity for physics fields:
Set scene.physicsWorld.gravity to SCNVector3Zero.
Add a SCNPhysicsField created with the linearGravityField constructor to your scene, and set its direction and strength to get the kind of gravity behavior you want.
Set the categoryBitMasks on both your sphere body and the gravity field such that the field affects other bodies but not the sphere.
Whichever of these methods you use, you can change them when you want to "turn gravity on" for the sphere: reduce the damping, reset the velocityFactor, or change the sphere's or the gravity field's categoryBitMask.
As of iOS 9, you can set the isAffectedByGravity property to false then flip the value to true to make the sphere drop: https://developer.apple.com/reference/scenekit/scnphysicsbody/1514738-isaffectedbygravity
setting a physics body to sphere only when you want to it to be affected by gravity should work.

Does SpriteKit apply physics if I modify the position and rotation of a SKSpriteNode directly without an SKAction?

For example if I want to do my own custom animation and move an SKSpriteNode every frame programmatically by x += 10, will Sprite Kit still apply physics correctly or must I always use SKAction?
Manually moving a node with a physics body is possible regardless of how or when you do it. But in any case it's not recommended since it can adversely affect the physics simulation. The node (view) could be out of sync with the position of the body for 1 frame, and you might move the body into a collision which the physics engine will forcibly resolve, causing jumps, jitter, exploding velocities or skipping collisions.
When you use physics, stick to moving the physics body through force, impulse or changing the body velocity directly.
No, you don't have to use SKAction to move SKSpriteNode. This approach works fine for me:
- (void)update:(CFTimeInterval)currentTime {
myNode.position = CGPointMake(myNode.position.x + 0.1,myNode.position.y);
}
All physics work as expected

How to implement mouse joint in Sprite Kit?

I have working implementation of drag and drop feature written in Cocos2d + Box2d for iOS. I need to port it to Sprite Kit. The logic is pretty basic:
when user touching the screen, find sprite under the finger
create mouse joint between found sprite and the scene's physics body, set joint's target to position of touch
when touches moved, update joint's target to new position
when touches ended, remove the joint
Everything is working quite well. I like the way physics is simulated when touches ends - the dragged shape has velocity dependent on dragging speed and direction, so when I left my finger from the screen while moving the sprite, it will continue moving in the same direction, but now affected by gravity and damping.
Unfortunately, I'm not able to reproduce the same effect using Sprite Kit. There is no such joint like mouse joint, but I have tried using other joint types. I almost succeeded with SKPhysicsJointFixed - I'm creating new sprite and joint between it and dragged sprite, when touches moving I'm moving the newly created sprite. Unfortunately it doesn't work like mouse joint in Cocos2d + Box2d - while dragging the sprite, its velocity always equals zero. So, every time I left my finger from the screen, the dragged sprite stops immediately and start falling affected by gravity. No matter how fast I move the finger when dragging, after releasing dragged shape, it behaves exactly the same way.
My question is how to implement mouse joint in Sprite Kit, or how to implement drag and drop feature that works like described above?
Update:
This is a box2d mouse joint example that could give you more clear view on what I am trying to implement using Sprite Kit:
http://blog.allanbishop.com/wp-content/uploads/2010/09/Box2DJointTutorial1.swf
I'm pretty new to iOS development so I guess this might not be the best way, but this is how I solved it and it seems to work pretty smooth actually.
I'm using a UIPanGestureRecognizer to handle the touch event. I set this one up in my didMoveToView: with this code:
UIPanGestureRecognizer *gestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handlePan:)];
An UIPanGestureRecognizer is passed to handlePan: and I check with recognizer.state == UIGestureRecognizerStateEnded if the touched stopped. This is where I want to "release" the object and "let it fly away". For doing so I needed to calculate a few things, first of all I get the velocity of the finger/touch with this code:
CGPoint velocity = [recognizer velocityInView:recognizer.view];
(I use the same method (handlePan:) with other statements for getting the right object when touch starts, and letting the object be under the finger all the time by setting the position of the object to the location of the touch when moving)
Now I know the velocity, but I still don't know how much force I need to apply to the object. You should be able to calculate the force by this formula: Force = mass * acceleration. We know the mass (object.physicsBody.mass) and we know the velocity. To get the acceleration we need the time as well because acceleration = velocity / time.
In my update: method that is called every time a new frame is to be rendered I calculate the difference between the last time a frame was about to be rendered by doing something like this:
self.delta = currentTime - self.lastTime;
self.lastTime = currentTime;
I now can calculate which force that is needed to get the object moving in the velocity I'm "dragging it in". To do this I do the following:
[self.currentNode.physicsBody applyForce:CGVectorMake(velocity.x/self.delta * self.currentNode.physicsBody.mass, -velocity.y/self.delta * self.currentNode.physicsBody.mass)];
I take velocity divided with the difference in time since last frame (self.delta) to get the acceleration, and then multiply it with the mass of the object to get the force needed to keep (or actually getting) the object moving in the same direction and in the same velocity as it was moved by my finger.
This is working for me at the moment and I hope it helps other people too, and please tell me if I got something wrong or if there is a better solution. So far I have not found any "native solution".
If the only issue is velocity, then that is simple to fix.
Subtract the current touch position from the previous touch position to get the velocity between the last and current touch. Use applyForce: to apply this velocity to the body.
In order to prevent the body from moving on its own while dragging, read out the new velocity from the body after calling applyForce: and keep it in an ivar and set the body's velocity to zero.
When the touch ends, set the body's velocity to the one you keep as an ivar.
If this doesn't work, try using applyImpulse: and/or setting the velocity before calling any of the two methods, ie:
sprite.physicsBody.velocity = bodyVelocity;
[sprite.physicsBody applyForce:touchVelocity];
bodyVelocity = sprite.physicsBody.velocity;
sprite.physicsBody.velocity = CGVectorMake(0, 0);
That way you can keep the body's velocity without losing control over it while dragging.

Resources