Swift 如何创建圆形物体进入并与厚物质分离的效果

Swift 如何创建圆形物体进入并与厚物质分离的效果,swift,sprite-kit,cadisplaylink,Swift,Sprite Kit,Cadisplaylink,基于下面的图像(我使用不同的颜色来表示圆形和平面,这样可以看到它们,但最终颜色将是相同的),使用Swift和Spritekit,我尝试创建圆形物体进入厚物质(不一定是粘性的)并与厚物质分离的效果。基本上,当圆形物体分离时,当它形成一个圆时,它会从平面上拉开。 我想使用图像动画帧,但由于对象是带有物理体的SKSpriteNodes,这将使对象与动画的碰撞计时变得非常困难。另一种方法是使用CAAnimation,但我不知道如何将其与物理体的SKSpriteNodes结合起来。我如何使用上述任何一种方

基于下面的图像(我使用不同的颜色来表示圆形和平面,这样可以看到它们,但最终颜色将是相同的),使用Swift和Spritekit,我尝试创建圆形物体进入厚物质(不一定是粘性的)并与厚物质分离的效果。基本上,当圆形物体分离时,当它形成一个圆时,它会从平面上拉开。 我想使用图像动画帧,但由于对象是带有物理体的SKSpriteNodes,这将使对象与动画的碰撞计时变得非常困难。另一种方法是使用CAAnimation,但我不知道如何将其与物理体的SKSpriteNodes结合起来。我如何使用上述任何一种方法或其他方法创建这种分离效果

更新

下图显示了圆形物体进入厚物质直至被淹没时,厚物质表面的变化


从较高的理解水平来看,有两种方法可以做到这一点

  • 不好的方法(但在流体具有纹理时效果更好):提前创建精灵表,然后覆盖SKSpriteNode对象的附加子对象。当球与曲面之间的距离小于某个值时,动画精灵中的帧将是球与曲面之间距离的函数。所需的距离范围(range)必须映射到精灵帧编号(frameIndex)。f(范围)=帧索引。线性插值在这里会有所帮助。稍后将详细介绍插值

  • 正确的方法是:使流体成为曲线对象,然后使用初始、中间和最终状态之间的线性插值设置曲线上点的动画。这将需要三条曲线,每条曲线具有相同的点数。让初始流体状态为F1。F1模型点作为静态流体。当球浸入一半时,让流体状态为F2。F2型点看起来像浸没在其最大宽度处的球。当球体浸没75%时,让流体状态为F3。请注意,当球完全浸入水中时,流体看起来没有变化。这就是为什么当球75%浸没在水中时,它会有最大的表面张力抓住球。就SpriteKit而言,您可以使用以下对象:

    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;
    
  • 然后,通过使用三维向量的向量叉积来检测球何时位于流体外部,即使项目是二维的

     Ball Vector (Vb)
        ^
        |
    (V) O--->  Closest Fluid Surface Vector (Vs)
    
    V = Vb x Vs
    
    然后看V的Z分量,称为Vz。 如果(Vz<0),则钢球位于流体外部: 创建一个变量t:

    t = distOfBall/radiusOfBall
    
    然后对流体形状中的每个有序点执行以下操作:

    newFluidPointX = F1pointX*(t-1) + F2pointX*t
    newFluidPointY = F1pointY*(t-1) + F2pointY*t
    
    如果Vz>0),则球位于流体内部:

    t = -(((distOfBall/radiusOfBall) + 0.5)^2)  *4 + 1
    newFluidPointX = F2pointX*(t-1) + F3pointX*t
    newFluidPointY = F2pointY*(t-1) + F3pointY*t
    
    这是因为可以使用插值将任意两个形状混合在一起。 参数“t”用作两个形状之间混合的百分比

    只要点数相同,实际上可以在任意两个形状之间创建无缝混合。这就是好莱坞电影中一个男人如何变成狼,或者一个男人如何变成一个液体水坑。这些效果的唯一原理是插值。插值是一个非常强大的工具。其定义如下:

        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.
    
    有关插值的详细信息,请参见:

    供进一步研究。如果你正在考虑动画任何动态形状,我会考虑理解贝塞尔曲线。Pomax有一篇关于这个主题的精彩文章。尽管许多框架中都有曲线,但对它们的工作原理有一个大致的了解将允许您对它们进行扩展性操作,或者在缺少框架的地方滚动您自己的特性。她的文章是Pomax的:

    祝您进步顺利:)

    您正在寻找流体模拟器 这在现代硬件中确实是可能的。 让我们看看我们将在这里建造什么

    组件 为了实现这一目标,我们需要

    • 创建几个具有物理体和模糊图像的分子
    • 使用着色器将公共颜色应用于alpha>0的每个像素
    分子 这是分子类

    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")
        }
    }
    
    着色器 接下来我们需要一个片段着色器,让我们创建一个名为
    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;
    }
    
    场景 最后我们可以定义场景

    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)
        }
    
    }
    
    导入SpriteKit
    类游戏场景:SKScene{
    惰性变量标签:SKLabelNode={
    将子节点(名称为“label”)返回为!SKLabelNode
    }()
    让effectNode=SKEffectNode()
    覆盖func didMove(到视图:SKView){
    physicsBody=SKPhysicsBody(edgeLoopFrom:frame)
    effectNode.shouldEnableEffects=true
    effectNode.shader=SKShader(文件名为:“水”)
    addChild(效应节点)
    }
    var接触位置:CGPoint?
    覆盖func TouchesBegind(Touchs:Set,带有事件:UIEvent?){
    guard let touch=touch.first else{return}
    让touchLocation=touch.location(in:self)
    if label.contains(触摸位置){
    addRedCircle(位置:touchLocation)
    }否则{
    self.touchLocation=touchLocation
    }
    }
    覆盖函数touchesend(touchs:Set,带有事件:UIEvent?){
    触摸位置=零
    }
    覆盖函数更新(uCurrentTime:TimeInterval){
    如果让touchLocation=touchLocation{
    设randomizeX=CGFloat(arc4random_均匀(20))-10
    设randomizedLocation=CGPoint(x:touchLocation.x+randomizeX,y:touchLocation.y)
    添加分子(位置:随机化位置)
    }
    }
    专用func addMolecule(位置:CGPoint){
    让分子=分子()
    位置=位置
    effectNode.addChild(分子)
    }
    专用func addRedCircle(位置:CGPoint){
    let texture=SKTexture(图像名为:“圆”)
    设sprite=SKSpriteNode(纹理:纹理)
    让physicsBody=SKPhysicsBody