How to create the effect of a circular object entering and separating from a thick substance

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

Leave a Comment