SpriteKit - Making a sprite defy gravity (like a balloon)
ios, sprite-kit
Solution
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.
Problem
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