42

Based on the image below (I used different colours for circle and flat surface so they can be seen, but in the end the colours will be the same), using Swift and Spritekit, I am trying to create the effect of a circular object entering a thick substance (not necessarily sticky) and separating from the thick substance. Basically, when the circular object is separating, it will pull away from the flat surface as it forms into a circle. I wanted to use image animation frames, but since the objects are SKSpriteNodes with physics bodies this will make timing the collision of objects with animation quite difficult. Another approach would be using CAAnimation, but I don't know how this can be combined with SKSpriteNodes with physics bodies. How can I create this separation effect using any of the above stated approaches or a different one?

enter image description here

UPDATE

The image below shows the change in the surface of the thick substance as the circular object enters the thick substance till it's submerged.

enter image description here

Himanshu
  • 2,832
  • 4
  • 23
  • 51
NSologistic
  • 745
  • 4
  • 14
  • 1
    I'm unclear as to what aspect of "entering a thick substance" you're trying to capture. The velocity of the circular object? The color as it encounters the "thick substance"? The surface tension of the thicker substance? Maybe include a drawing of the effect you're looking for... – Rob Feb 15 '16 at 09:28
  • 2
    @Rob I am referring to the surface tension of the thicker substance – NSologistic Feb 15 '16 at 09:45
  • Is this something you are after? http://pasteboard.co/1zoNKPiJ.png I have two circles there that you drag and connect smoothly. I create a custom shape between two circles using a bezier path and some geometry. – konrad.bajtyngier Feb 15 '16 at 10:33
  • @Rob Thanks for the recommendation. I just updated the question. – NSologistic Feb 18 '16 at 14:42
  • @konrad.bajtyngier That is a bit similar to what I want to achieve when separating the circular object from the flat surface – NSologistic Feb 18 '16 at 14:45
  • just noticed your updates question. a bezier path with Core Graphics could be a way to go. i update the path every time my gesture recognizer calls and is in .Changed state. this is an example of my custom shape (in black) http://pasteboard.co/1EeGoSNX.png – konrad.bajtyngier Feb 18 '16 at 14:52
  • Represent substance as tiny particles, connected to each other (simulating tension). – Sulthan Mar 10 '16 at 15:14
  • I would investigate what is offered from available game engines out there. I am sure between SpriteKit, Unity and Unreal 4 there is something you can use. – Daniele Bernardini Mar 10 '16 at 15:34
  • Just throwing this out there, have you had a look at "Liquid Fun". It might bump you in the right direction. https://www.raywenderlich.com/85515/liquidfun-tutorial-1 – the_pantless_coder Mar 10 '16 at 20:29
  • I'm just assuming that you'd need to know a great deal of math (Bezier paths?) or physics (fluid mechanics?) to come up with your own solution. But maybe some genius already has a framework or source code you can use. – Fine Man Mar 12 '16 at 20:40
  • 1
    @konrad.bajtyngier were you able to separate the two shapes in the image you showed? If yes, do you mind sharing some more insight or code on how you did it. – Jickery Apr 02 '16 at 16:51
  • Why dont you try this https://github.com/MengTo/Spring – itechnician Apr 19 '16 at 13:45
  • You can use this as reference : https://designcode.io/swiftapp – itechnician Apr 19 '16 at 13:47

2 Answers2

2

You are looking for a fluid simulator

This is indeed possible with modern hardware. Let's have a look at what we are going to build here.

enter image description here

Components

In order to achieve that we'll need to

  • create several molecules having a physics body and a blurred image
  • use a Shader to apply a common color to every pixel having alpha > 0

Molecule

Here's the Molecule class

import SpriteKit

class Molecule: SKSpriteNode {

    init() {
        let texture = SKTexture(imageNamed: "molecule")
        super.init(texture: texture, color: .clear, size: texture.size())

        let physicsBody = SKPhysicsBody(circleOfRadius: 8)
        physicsBody.restitution = 0.2
        physicsBody.affectedByGravity = true
        physicsBody.friction = 0
        physicsBody.linearDamping = 0.01
        physicsBody.angularDamping = 0.01
        physicsBody.density = 0.13
        self.physicsBody = physicsBody

    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

Shader

Next we need a Fragment Shader, let's create a file with name Water.fsh

void main() {

    vec4 current_color = texture2D(u_texture, v_tex_coord);

    if (current_color.a > 0) {
        current_color.r = 0.0;
        current_color.g = 0.57;
        current_color.b = 0.95;
        current_color.a = 1.0;
    } else {
        current_color.a = 0.0;
    }

    gl_FragColor = current_color;
}

Scene

And finally we can define the scene

import SpriteKit

class GameScene: SKScene {

    lazy var label: SKLabelNode = {
        return childNode(withName: "label") as! SKLabelNode
    }()

    let effectNode = SKEffectNode()

    override func didMove(to view: SKView) {
        physicsBody = SKPhysicsBody(edgeLoopFrom: frame)
        effectNode.shouldEnableEffects = true
        effectNode.shader = SKShader(fileNamed: "Water")
        addChild(effectNode)
    }

    var touchLocation: CGPoint?

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        guard let touch = touches.first else { return }
        let touchLocation = touch.location(in: self)
        if label.contains(touchLocation) {
            addRedCircle(location: touchLocation)
        } else {
            self.touchLocation = touchLocation
        }
    }

    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        touchLocation = nil
    }


    override func update(_ currentTime: TimeInterval) {
        if let touchLocation = touchLocation {
            let randomizeX = CGFloat(arc4random_uniform(20)) - 10
            let randomizedLocation = CGPoint(x: touchLocation.x + randomizeX, y: touchLocation.y)
            addMolecule(location: randomizedLocation)
        }
    }

    private func addMolecule(location: CGPoint) {
        let molecule = Molecule()
        molecule.position = location
        effectNode.addChild(molecule)
    }

    private func addRedCircle(location: CGPoint) {
        let texture = SKTexture(imageNamed: "circle")
        let sprite = SKSpriteNode(texture: texture)
        let physicsBody = SKPhysicsBody(circleOfRadius: texture.size().width / 2)
        physicsBody.restitution = 0.2
        physicsBody.affectedByGravity = true
        physicsBody.friction = 0.1
        physicsBody.linearDamping = 0.1
        physicsBody.angularDamping = 0.1
        physicsBody.density = 1
        sprite.physicsBody = physicsBody
        sprite.position = location
        addChild(sprite)
    }

}

The full project is available on my GitHub account https://github.com/lucaangeletti/SpriteKitAqua

Luca Angeletti
  • 58,465
  • 13
  • 121
  • 148
  • Hi, I am looking to build something like this and your GitHub link looks to be broken/repo deleted. Could you share any similar GitHub repo if possible ? – SriTeja Chilakamarri Oct 24 '22 at 10:32
1

From a high level of understanding there are two ways to do this.

  1. THE BAD WAY (but works better when fluid has textures): Create the sprite sheet in advance, then overlay an additional child of the SKSpriteNode object. The frame in the animation sprite will be a function of distance from the ball to the surface when distance between them is less than some amount. The desired distance range (range) will have to be mapped to the sprites frame number (frameIndex). f(range) = frameIndex. Linear interpolation will help out here. More on interpolation later.

  2. THE RIGHT WAY: Make the fluid a curve object, then animate the points on the curve with linear interpolation between the initial, intermediate and final states. This will require three curves each one with the same number of points. Let the initial fluid state be F1. Model F1 points as the static fluid. Let the fluid state be F2 when the ball is halfway submerged. Model F2 points to look like the ball submerged at its maximum width. Let the fluid state be F3 when the ball is 75% submerged. Notice that when the ball is fully submerged the fluid looks unchanged. This is why when the ball is 75% submerged it has the maximum surface tension grabbing the ball. As far as SpriteKit goes, you may use these objects:

    CGMutablePathRef path = CGPathCreateMutable();
    CGPathMoveToPoint(path, NULL, 0, 0);
    CGPathAddQuadCurveToPoint(path, NULL, 50, 100, 100, 0);
    CGPathAddLineToPoint(path, NULL, 50, -100);
    CGPathCloseSubpath(path);
    
    SKShapeNode *shape = [[SKShapeNode alloc]init];
    shape.path = path;
    

Then detect when the ball is on the outside of the fluid by using the vector cross product with 3D vectors, even if your project is in 2D.

 Ball Vector (Vb)
    ^
    |
(V) O--->  Closest Fluid Surface Vector (Vs)

V = Vb x Vs

Then look at the Z component of V called Vz. If (Vz < 0), the ball is outside of the fluid: Create a variable t:

t = distOfBall/radiusOfBall

Then for every ordered point in your fluid shapes do the following:

newFluidPointX = F1pointX*(t-1) + F2pointX*t
newFluidPointY = F1pointY*(t-1) + F2pointY*t

If Vz > 0), the ball is inside of the fluid:

t = -(((distOfBall/radiusOfBall) + 0.5)^2)  *4 + 1
newFluidPointX = F2pointX*(t-1) + F3pointX*t
newFluidPointY = F2pointY*(t-1) + F3pointY*t

This works because any two shapes can be blended together using interpolation. The parameter "t" acts as a percentage to blend between two shapes.

You can actually create seamless blends between any two shapes, so long as the number of points are the same. This is how a man morphs into a wolf in Hollywood movies, or how a man can morph into a liquid puddle. The only principle in play for those effects is interpolation. Interpolation is a very powerful tool. It is defined as:

    L = A*(t-1) + B*t
    where t is in between 0.0 and 1.0
    and A and B is what you are morphing from and to.

For more on interpolation see: Wiki Article

For further study. If you are considering animating any dynamic shapes, I would consider understanding Bezier curves. Pomax has a wonderful article on the topic. Although many frameworks have curves in them, having a general understanding of how they work will allow you to manipulate them extensivly or roll your own features where the framework is lacking. Her is Pomax's article:

A Primer on Curves

Good luck on your progress:)