Does anyone have any idea how I can make my SKSpriteNode defy gravity? I thought of inverting the default gravity but realise I also need things to fall too!
It seems like it should be easy but reading through the documentation I can’t see how I would do it!
Thanks
Update: In iOS 8 / OS X Yosemite (10.10), physics fields provide an elegant solution to these sorts of problems. Here's a quick take on using them to add buoyancy (for a specific set of nodes) to your scene.
Create an SKFieldNode with the linearGravityFieldWithVector constructor, providing a vector that's the opposite of gravity, and add the field node to your scene.
Set the fieldBitMask on your balloons to something unique.
Set the categoryBitMask on the field to something that overlaps with the balloons' fieldBitMask, but that does not overlap with the fieldBitMask of any other bodies.
Now, the balloons will rise or hold steady, but other objects will fall. Tweaking the field's strength will let you tune whether the balloons' buoyancy is perfectly balancing gravity (so that they float in place, but are disturbed when touched), or slightly more or less than gravity (so that they slowly rise or fall).
By default, a field is infinite, covering the whole scene, but you can change that with the field's region property to limit it to a portion of the scene. (This is useful if you want to simulate buoyancy in water — once an object rises past the top of the field at the water's surface, it falls back in.)
Also, if you want variable buoyancy as per #TheisEgeberg's answer, you can control its variation over distance with the falloff property.
In iOS 7 / OS X Mavericks (10.9), or if you want more precise control over which forces apply where and when, you can use the approach from my original answer below.
If you want an object to really float like a balloon — that is, to be buoyant, affected by gravity but also counteracting it — you'll need to apply a force to it on every frame (i.e. in your update: method).
Beware scaling: gravity is a constant acceleration, but if you're applying a force to counteract gravity, a force is proportional to mass. To make a vector that perfectly balances gravity for use in applyForce:, you'll need to:
scale the gravity vector by {-1,-1,-1} to point in the opposite direction
scale by the mass of the body you're applying the force to (F = ma, where gravity or anti-gravity is a).
scale by 150 — there's a bug where the SKPhysicsWorld.gravity property isn't in the same units as applyForce:. (If you turn SKPhysicsWorld gravity off and use SKFieldNode gravity instead, you don't need to do this.)
Unlike turning off affectedByGravity and applying an action to make the balloon rise, this approach works well with the rest of the physics sim. With a balancing force exactly equal to gravity, the balloon will float in place — after colliding with other things it'll return to equilibrium. If the balancing force is greater than gravity, the balloon will rise, but its rise will be hindered by other bodies in its way.
First off, an SKSpriteNode isn't affected by gravity at all. It is the SKPhysicsBody that belongs to the node that is affected by gravity.
Second...
myNode.physicsBody.afectedByGravity = NO;
:D
If you want it to rise upwards then you can add an action to it...
SKAction *moveAction = [SKAction moveByX:0 y:-10 duration:1];
SKAction *repeatingAction = [SKAction repeatActionForever:moveAction];
[myNode runAction:repeatingAction];
As many have said it's about counterforce.
So yes, you can apply a counterforce to the balloon to make it go upwards.
But to make it look like a balloon you need to understand what makes a balloon go up: Air pressure. Since the helium or whatever light gas you are using is lighter than air it will start to go up, or in other words the heavier air will go under the balloon. It's like a piece of wood in water, the heavier water will go under the wood, till the wood is soaked and gets even heavier than the water.
So what you should do is to make the counterforce adapt to the height of the balloon, the higher it gets the less pressure you apply upwards. This is a way to simulate buoyancy.
Here’s my answer:
- (MyClass *)newRisingObject
{
MyClass *risingObject = [MyClass spriteNodeWithImageNamed:#"image"];
[risingObject setPosition:CGPointMake(CGRectGetMidX(self.frame), CGRectGetMinY(self.frame))];
[risingObject setName:#"name"];
[risingObject setUserInteractionEnabled:YES];
// Physics
//
[risingObject setPhysicsBody:[SKPhysicsBody bodyWithCircleOfRadius:risingObject.size.width / 2.0f]];
[risingObject.physicsBody setRestitution:1.0f];
[risingObject.physicsBody setFriction:0.0f];
[risingObject.physicsBody setLinearDamping:0.0f];
[risingObject.physicsBody setAngularDamping:0.0f];
[risingObject.physicsBody setMass:1.3e-6f];
return risingObject;
}
.
- (void)didMoveToView:(SKView *)view
{
/* Setup your scene here */
MyClass *risingObject = [self newRisingObject];
[self addChild:risingObject];
}
.
- (void)update:(CFTimeInterval)currentTime
{
/* Called before each frame is rendered */
[self enumerateChildNodesWithName:#"name"
usingBlock:^(SKNode *node, BOOL *stop)
{
// Push object up
//
[node.physicsBody applyImpulse:CGVectorMake(0.0f, node.physicsBody.mass * OBJECT_ACCELERATION)];
}];
}
Related
I have made a game using SpriteKit and Swift 3 and have figured out all aspects of the game except the speed of the ball node in the game. Im confused with the different function applyImpulse() and ball.physicsBody.velocity, as I have tested both and don't seem to really understand what the speed I'm actually programatically settings is. Any clarification on what I should be using would be great?
Also whilst testing (by printing the ball's velocity to the console every collision) I would see sometimes the ball's speed would simply go to some long and random decimal value when it hit other nodes such as a paddle which I hadn't specifically coded anything to happen with the ball's speed in the case of a collision with it.
In summary I would appreciate:
Just general clarification regarding speed of the ball in SpriteKit and how I should approach it (what method/function I should use)
How I would make it so the ball's speed doesn't got to these very long random decimals
Thanks
In regards to the values, there is not really a set rule of what the values are for impulses and forces. It depends on how big your sprites physics body are etc. An impulse of 80 might be a perfect jump value for 1 sprite size, but than make it half the size and that 80 is suddenly way to high. There are also factors such as gravity, mass etc than can have an effect on this.
So you usually just play around with the values until you get the desired result.
In regards to the collision with the paddle, you need to check your bit mask values and your dynamic properties. SpriteKit by default sets collisions to all objects, so if you dont specifically tell your paddle/ball to ignore each other they will collide.
There are also things such as restitution, friction, damping etc that can have an effect on how you sprites behave when colliding.
There are loads of tutorials on google about SpritKit physic/collisions or read the apple documentation.
In regards to the difference between velocity and impulses/forces, as per apples documentation
"First, you can control a physics body’s velocity directly, by setting its velocity and angularVelocity properties. As with many other properties, you often set these properties once when the physics body is first created and then let the physics simulation adjust them as necessary. For example, assume for a moment you are making a space-based game where a rocket ship can fire missiles. When the ship fires a missile, the missile should have a starting velocity of the ship plus an additional vector in the direction of the launch.
When a body is in the simulation, it is more common for the velocity to be adjusted based on forces applied to the body. Another source of velocity changes, collisions, is discussed later."
https://developer.apple.com/library/content/documentation/GraphicsAnimation/Conceptual/SpriteKit_PG/Physics/Physics.html
So basically the general rule of thumb is this:
1) Only set the velocity property when you create the physics body. I never needed to do this for my games yet.
The only time I really use the velocity property is for things such as double jumping where I need to set it to 0 to have a consistent double jump
...velocity.dy = 0
...applyImpulse(
2) When you are playing the game already than
a) If you are trying to continuously move your ball you should use
applyForce...
in something like the update method of your SKScene.
b) If you want to make your ball jump, so basically a short 1 time thing, you should use
applyImpulse...
Hope this helps
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.
I have my first game application in development. In this game, the only character that the user controls will get to jump from one block to another. It's like Mario (in Mario Brothers) jumping from one moving lift to another. If he fails, he'll die. So how could you tell a free fall from a short fall as a result of a successful jump? One thing I thought I could do is measuring character's vertical velocity. So I have the following lines of code. It's used with didSimulatePhysics
SKNode *player = [self childNodeWithName:#"//player"]; // It's the node characterizing the game character
CGVector v = player.physicsBody.velocity;
if (v.dy < -2000) {
[self endTheScene:kEndReasonLose]; // The character has died from free fall => game is over
}
When the game character jumps, the game application can record a vertical velocity of -2022.466797. So this measure won't work. What else can I do? Set an invisible bar and see if the game character has touched it with intersectsNode? That can also fail. I have never developed a game before. So I don't know how they do it, which kind of makes me realize how impressive Nintendo game developers are. Some 30 years later, I still can't do it.
Thank you for your advice.
Update
The following can tell whether or not the character has died from free fall, I think.
- (void)didSimulatePhysics {
if (self.isMoving) {
// isMoving is an instance variable (BOOL): YES if the game has started
CGVector v = player.physicsBody.velocity;
BOOL hasFallen = self.lastFallenDate ? [self.lastFallenDate timeIntervalSinceNow] < -2.0 : YES; // lastFallenDate is an instance variable recording the time when the character fell last time
if (hasFallen) {
if (v.dy < -1500) {
[self endTheScene:kEndReasonLose];
}
}
}
}
Yet, I think Apple has a bug to fix as far as SKAction is concerned. No matter I do, audio will kick in exactly about 5 seconds after the game started although the character is not falling.
- (void)endTheScene:(EndReason)endReason {
if (endReason == kEndReasonLose) {
SKAction *lossAudio = [SKAction playSoundFileNamed:#"failureAudio.caf" waitForCompletion:NO];
[self runAction:lossAudio];
}
}
Super Mario Bros was a tile-based game: the screen was divided up into a number of square regions each of which being represented by a value. Each tile would have a separate sprite blitted to that region of the screen depending on the tile id, and based on that tile's properties Shigeru et al could determine if mario could stand on it, if it would hurt him or if he was free to fly. There's no magic to this, you just have to check where he is!
If you aren't concerned with the damage your Mario will take (i.e can he land a jump from any height as long as it is onto solid ground) this is easy: If his y position is ever lower than the lowest solid platform in your world, he has died:
Ignore velocity; check y position.
If you don't want him to survive a jump from the top of a building, you need to look at his velocity: Is he travelling fast enough that the force of impact would end his life?:
Ignore y position; check velocity
How do you know he is on a platform? A physic node?
Honestly, you can put a 'floor' plane stretching your world and anchor it at the bottom of your scene to catch dying plumbers and it won't impact performance - infact it will be more efficient than running these checks in your Update method.
Edit to add
Don't bother with intersections, -(void)didBeginContact:(SKPhysicsContact*)contact will alert you to any collision that occurred as long as you are a delegate. Skip the update loop completely: this will trigger for any collisions and won't be missed
Code --> http://pastebin.com/W3DMYjXa
I am tinkering with SpriteKit and I cannot seem to get an emitter node to rotate with my spaceship the way I would like.
I watched the Apple Tech Talks Session where they described making a spaceship and using an emitter for the thrust exhaust, but they lock the ship to only face one direction.
In my experiment I am trying to allow the player sprite (spaceship) to travel in any direction, I have rotation and scrolling working for the player, but the particle emitter doesn't seem to rotate with the player.
my hierarchy looks something like this
Scene
-->World Node
-->Player Node
---->Player Sprite
---->Emitter Node
My theory is that If I rotate (Player Node) it should rotate both of its children, and it does rotate them, but the emitter continues to emit in the same direction.
I can change the emission angle manually, but it seems needlessly complicated.
here is what I am using to rotate
-(void)rotatePlayerToDirection:(TTDirection)direction {
CGFloat radDir;
CGFloat emiDir;
scrollDirection = direction;
switch (direction) {
case TTUp:
radDir = 0;
emiDir = 3.142;
break;
case TTRight:
radDir = 4.712;
emiDir = 1.571;
break;
case TTDown:
radDir = 3.142;
emiDir = 0;
break;
case TTLeft:
radDir = 1.571;
emiDir = 4.712;
break;
default:
break;
}
SKAction *rotatePlayer = [SKAction rotateToAngle:radDir duration:0.1 shortestUnitArc:YES];
[playerNode runAction:rotatePlayer];
}
Am I missing something here?
Here is a video of this in action
http://youtu.be/NGZdlB9-X_o
I'm fairly certain there is a bug in spritekit to do with the targetNode and rotation. The other answer seems to suggest this is expected behavior, but that ignores that the docs explicitly give this situation as a motivation for the existence of targetNode.
Working with Other Node Types
What you really want is for the particles to be spawned, but thereafter be
independent of the emitter node. When the emitter node is
rotated, new particles get the new orientation, and old
particles maintain their old orientation.
It then gives an example using targetEmitter on how to achieve this. However, the example code does not work.
It seems that setting targetNode breaks particle rotation.
UPDATE: I found a workaround.
Spritekit is failing to adjust the SKEmitterNode.emissionAngle property when SKEmitterNode.targetNode is set. You can work around this by setting it manually after actions have been processed.
Assuming that your SKEmitterNode has only one parent, you can do something like this in your scene.
- (void)didEvaluateActions
{
// A spaceship with a thrust emitter as child node.
SKEmitterNode *thrust = (SKEmitterNode *)[self.spaceship childNodeWithName:#"thrust"];
thrust.emissionAngle = self.spaceship.zRotation + STARTING_ANGLE;
}
Note that if there are multiple ancestor nodes which may be rotated, you will need to loop through them and add all of their zRotations together.
I know this is an old question, and the OP has probably found a solution or workaround, but I thought I would add by two pence worth, in case it helps anybody else.
I am just starting out with SpriteKit and Swift, and am developing a game where nodes fly around and collide with each other, changing direction of travel frequently.
I add an emitter to each node, and initially found that as the node rotated and changed direction, the emission 'trail' remained fixed. I then read that the emitter should have its target node set not as the node it is a child of, but the scene they both exist in.
That at least made the particle trail twist and turn realistically rather than just spurt out in one direction.
But the nodes can spin and changes direction, and I wanted the particles to trail away from the node in the opposite direction to its travel - just like a smoke trail from a plane or rocket.
As the node may rotate, setting the emitters angle of emission to the z-rotation of the node does not work. So...
I track all onscreen nodes in a dictionary, removing them from the scene as they travel off the screen.
So in didSimulatePhysics I use the same dictionary to get the node and calculate the angle of travel from the velocity vector, and then set the emitters emission angle accordingly :
import Darwin
// in your scene class...
override func didSimulatePhysics() {
removeOffScreenParticles()
for (key,particle) in particles {
let v = particle.physicsBody!.velocity
particle.trail!.emissionAngle = atan2(v.dy, v.dx) - CGFloat(M_PI)
}
}
I am aware that this calculation and adjustment is being performed for each on screen node, in every frame, so will probably use my frame counter to only do this check every n'th frame if performance becomes an issue.
I hope this helps someone!
You are setting the emitter's targetNode property to the background. Note that this forces the emitter's particles to be rendered by the background node, and they will be treated as if the background was their parent. So, the particles aren't changing the angle, because the rotation of the background does not change.
Remove [emitter setTargetNode:backgroundNode] and the emitter's emission angle should be rotating correctly along with the player.
If you're looking for a 'middle ground', where the angle rotates along with the player, while the already-rendered particles aren't 'stuck' to the emitter, try this:
First, do not set the targetNode on the emitter (it defaults to nil).
Second, just as you are about to run the rotation action, temporarily set the targetNode to another node, and reset it back to nil when it completes (so that the particles can rotate along):
emitter.targetNode = backgroundNode;
[playerNode runAction:rotatePlayer completion:^{
emitter.targetNode = nil;
}];
Note that the results are also 'middle ground' - the emitter 'puffs' as the target node changes (the previous particles are removed, and new ones are rendered by the new target node). Hope that helps.
Other than that, there's no really straightforward way of achieving this - setting the emission angle manually might be the simplest way, after all. Here's one way to do it, right before running the rotation (the emitter target node is still nil, also, there's no need to hardcode emission angles):
float originalAngle = emitter.emissionAngle;
emitter.emissionAngle = emitter.emissionAngle - radDir;
[playerNode runAction:rotatePlayer completion:^{
emitter.emissionAngle = originalAngle;
}];
Is there a way to make a sprite rotate after contact with other sprites but without having frictional forces applied to it?
If i set
sprite.physicsBody.friction = 0.0;
sprite.physicsBody.allowsRotation = YES;
no rotation occurs.
If you want a body to rotate upon sliding contact with other bodies, some friction is necessary. (Just like in real-world physics!) Do you want friction to cause rotation, but exhibit frictionless behavior once it's rotating? If so, rereading the previous sentence should give a clue to the answer: you need to change the friction coefficient after the body starts rotating. Setting a contact delegate gives you an opportunity to make this change. It's also good for just about any other case where you want to "fudge" the results of a collision after it occurs, such as re-setting the body's velocity to a predetermined value.
If you want to manage the rotation by yourself, you can detect the collision with the delegate and the apply angular impulse like this
[sprite.physicsBody applyAngularImpulse:0.05];
Maybe I'm not getting the point but what you seem to want to do is really just:
sprite.zRotation = 1.0; // Note: zRotation is in radians
Or use SKAction rotateToAngle:duration: if you want the rotation to occur over a period of time.