There are three possible options I believe you have with regards to simulating water.
1) As mentioned in the comments you could try to use SKFieldNode (iOS 8+). But from personal experience the field node didn't really do it for me because you don't get much control over your simulation with it unless you heavily customize it, in which case you might as well just do your own calculations from scratch and reduce complexity.
2) You could adjust the linear and rotational damping of your sprite when inside the water. In fact, even apple mentions this in the quote from their documentation. However this won't give you buoyancy.
The linearDamping and angularDamping properties are used to calculate
friction on the body as it moves through the world. For example, this
might be used to simulate air or water friction.
3) Perform the calculations yourself. In the update method, check when the body enters you "water" and when it does you can calculate viscosity and/or buoyancy and adjust the velocity of your node accordingly. This in my opinion is the best option but also the more difficult.
Edit: I just wrote a quick example of option 3 in Swift. I think it's what you are looking for. I added factor constants on the top so you can adjust it to get exactly what you want. The motion is applied dynamically so it won't interfere with you current velocity (i.e. you can control your character while in the water). Below is the code for the scene and a gif as well. Keep in mind that the delta time is assumed to be 60 frames a second (1/60) and there is no velocity clamping. These are features you may or may not want depending on your game.
Swift
class GameScene: SKScene {
//MARK: Factors
let VISCOSITY: CGFloat = 6 //Increase to make the water "thicker/stickier," creating more friction.
let BUOYANCY: CGFloat = 0.4 //Slightly increase to make the object "float up faster," more buoyant.
let OFFSET: CGFloat = 70 //Increase to make the object float to the surface higher.
//MARK: -
var object: SKSpriteNode!
var water: SKSpriteNode!
override func didMoveToView(view: SKView) {
object = SKSpriteNode(color: UIColor.whiteColor(), size: CGSize(width: 25, height: 50))
object.physicsBody = SKPhysicsBody(rectangleOfSize: object.size)
object.position = CGPoint(x: self.size.width/2.0, y: self.size.height-50)
self.addChild(object)
water = SKSpriteNode(color: UIColor.cyanColor(), size: CGSize(width: self.size.width, height: 300))
water.position = CGPoint(x: self.size.width/2.0, y: water.size.height/2.0)
water.alpha = 0.5
self.addChild(water)
self.physicsBody = SKPhysicsBody(edgeLoopFromRect: self.frame)
}
override func update(currentTime: CFTimeInterval) {
if water.frame.contains(CGPoint(x:object.position.x, y:object.position.y-object.size.height/2.0)) {
let rate: CGFloat = 0.01; //Controls rate of applied motion. You shouldn't really need to touch this.
let disp = (((water.position.y+OFFSET)+water.size.height/2.0)-((object.position.y)-object.size.height/2.0)) * BUOYANCY
let targetPos = CGPoint(x: object.position.x, y: object.position.y+disp)
let targetVel = CGPoint(x: (targetPos.x-object.position.x)/(1.0/60.0), y: (targetPos.y-object.position.y)/(1.0/60.0))
let relVel: CGVector = CGVector(dx:targetVel.x-object.physicsBody.velocity.dx*VISCOSITY, dy:targetVel.y-object.physicsBody.velocity.dy*VISCOSITY);
object.physicsBody.velocity=CGVector(dx:object.physicsBody.velocity.dx+relVel.dx*rate, dy:object.physicsBody.velocity.dy+relVel.dy*rate);
}
}
override func touchesEnded(touches: NSSet!, withEvent event: UIEvent!) {object.position = (touches.anyObject() as UITouch).locationInNode(self);object.physicsBody.velocity = CGVectorMake(0, 0)}
}
Objective-C
#import "GameScene.h"
#define VISCOSITY 6.0 //Increase to make the water "thicker/stickier," creating more friction.
#define BUOYANCY 0.4 //Slightly increase to make the object "float up faster," more buoyant.
#define OFFSET 70.0 //Increase to make the object float to the surface higher.
@interface GameScene ()
@property (nonatomic, strong) SKSpriteNode* object;
@property (nonatomic, strong) SKSpriteNode* water;
@end
@implementation GameScene
-(void)didMoveToView:(SKView *)view {
_object = [[SKSpriteNode alloc] initWithColor:[UIColor whiteColor] size:CGSizeMake(25, 50)];
self.object.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:self.object.size];
self.object.position = CGPointMake(self.size.width/2.0, self.size.height-50);
[self addChild:self.object];
_water = [[SKSpriteNode alloc] initWithColor:[UIColor cyanColor] size:CGSizeMake(self.size.width, 300)];
self.water.position = CGPointMake(self.size.width/2.0, self.water.size.height/2.0);
self.water.alpha = 0.5;
[self addChild:self.water];
self.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:self.frame];
}
-(void)update:(NSTimeInterval)currentTime {
if (CGRectContainsPoint(self.water.frame, CGPointMake(self.object.position.x,self.object.position.y-self.object.size.height/2.0))) {
const CGFloat rate = 0.01; //Controls rate of applied motion. You shouldn't really need to touch this.
const CGFloat disp = (((self.water.position.y+OFFSET)+self.water.size.height/2.0)-((self.object.position.y)-self.object.size.height/2.0)) * BUOYANCY;
const CGPoint targetPos = CGPointMake(self.object.position.x, self.object.position.y+disp);
const CGPoint targetVel = CGPointMake((targetPos.x-self.object.position.x)/(1.0/60.0), (targetPos.y-self.object.position.y)/(1.0/60.0));
const CGVector relVel = CGVectorMake(targetVel.x-self.object.physicsBody.velocity.dx*VISCOSITY, targetVel.y-self.object.physicsBody.velocity.dy*VISCOSITY);
self.object.physicsBody.velocity=CGVectorMake(self.object.physicsBody.velocity.dx+relVel.dx*rate, self.object.physicsBody.velocity.dy+relVel.dy*rate);
}
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
self.object.position = [(UITouch*)[touches anyObject] locationInNode:self];
self.object.physicsBody.velocity = CGVectorMake(0, 0);
}
@end
SKFieldNode
, but I'll take a look. Thanks! – Plerre