使用 Swift + SpriteKit 将节点移动到手指

Posted

技术标签:

【中文标题】使用 Swift + SpriteKit 将节点移动到手指【英文标题】:Move a node to finger using Swift + SpriteKit 【发布时间】:2014-08-13 04:17:11 【问题描述】:

更新:我已经解决了这个问题,并找到了一种比提供的答案更简单的方法。我的解决方案是让太空船的速度等于它与我手指触摸的距离。为了更快地移动,您可以将此速度乘以一个常数。在这种情况下,我使用了 16。我还摆脱了在 touchesEnd 事件中将 lastTouch 设置为 nil。这样,即使我松开手指,船仍然会停下来。

override func update(currentTime: CFTimeInterval) 
/* Called before each frame is rendered */
    if let touch = lastTouch 
        myShip.physicsBody.velocity = CGVector(dx: (lastTouch!.x - myShip.position.x) * 16, dy: 0)
    


=================================

我有一个 SPACESHIP 节点,其运动限制在 X 轴上。当用户按下并按住屏幕上的某处时,我希望太空船能够移动到手指的 x 坐标,并且在手指松开之前不会停止向手指移动。如果 SPACESHIP 靠近用户手指并且用户手指仍然按下,我希望它逐渐减速并停止。我还希望在太空船改变方向、启动和停止时应用这种平滑的运动。

我正在尝试找出最好的方法。

到目前为止,我已经创建了节点并且它移动正确,但是有一个问题:如果我在屏幕上按住并按住,船最终会越过我的手指并继续移动。这是因为只有在我移动手指时才会触发改变船方向的逻辑。所以本质上,将我的手指移动到船上来改变船的方向是可行的,但是如果船越过我静止的手指,它不会改变方向

我需要 SPACESHIP 节点来识别它何时越过我静止的手指,并根据它与我手指的距离来改变它的方向或停止。

以下是相关代码:

第 1 部分:当用户按下时,找出触摸的来源并使用速度相应地移动 myShip (SPACESHIP)

override func touchesBegan(touches: NSSet, withEvent event: UIEvent) 
    /* Called when a touch begins */
    let touch = touches.anyObject() as UITouch
    let touchLocation = touch.locationInNode(self)

    if (touchLocation.x < myShip.position.x) 
        myShip.xVelocity = -200
     else 
        myShip.xVelocity = 200
    

第 2 部分当用户移动他们的手指时,触发一个事件来检查手指现在是否已移动到船的另一侧。如果是这样,改变船的方向。

override func touchesMoved(touches: NSSet!, withEvent event: UIEvent!) 
    let touch = touches.anyObject() as UITouch
    let touchLocation = touch.locationInNode(self)

    //distanceToShip value will eventually be used to figure out when to stop the ship
    let xDist: CGFloat = (touchLocation.x - myShip.position.x)
    let yDist: CGFloat = (touchLocation.y - myShip.position.y)
    let distanceToShip: CGFloat = sqrt((xDist * xDist) + (yDist * yDist))

    if (myShip.position.x < touchLocation.x) && (shipLeft == false) 
        shipLeft = true
        myShip.xVelocity = 200
    

    if (myShip.position.x > touchLocation.x) && (shipLeft == true) 
        shipLeft = false
        myShip.xVelocity = -200
    


第 3 部分当用户从屏幕上松开手指时,我希望飞船停止移动。

override func touchesEnded(touches: NSSet!, withEvent event: UIEvent!) 
    myShip.xVelocity = 0

第 4 部分更新改变船舶位置的事件

    override func update(currentTime: CFTimeInterval) 
/* Called before each frame is rendered */
    let rate: CGFloat = 0.5; //Controls rate of motion. 1.0 instantaneous, 0.0 none.
    let relativeVelocity: CGVector = CGVector(dx:myShip.xVelocity - myShip.physicsBody.velocity.dx, dy:0);
    myShip.physicsBody.velocity = CGVector(dx:myShip.physicsBody.velocity.dx + relativeVelocity.dx*rate, dy:0);

感谢阅读,期待回复!

【问题讨论】:

对于任何在这里搜索的人,我为这个老问题提供了一个正确/现代的答案,希望对您有所帮助 【参考方案1】:

您可以使用:myShip.physicsBody.applyImpluse(vector) 为自己省去很多麻烦。它的工作原理就像您向vector 方向推动myShip 点一样。如果您将vector 计算为从您上次触摸位置到myShip 的x 距离,那么它将加速、减速、改变方向等,非常接近您所描述的方式,因为它会给它一点在每个update 上朝着正确的方向推进。

基本上您存储最后一次触摸位置,然后在您的update 函数中计算从myShip 指向lastTouchCGVector 并将其作为脉冲应用到您的物理体。

类似:

var lastTouch: CGPoint? = nil

override func touchesBegan(touches: NSSet, withEvent event: UIEvent) 
    let touch = touches.anyObject() as UITouch
    let touchLocation = touch.locationInNode(self)
    lastTouch = touchLocation


override func touchesMoved(touches: NSSet!, withEvent event: UIEvent!) 
    let touch = touches.anyObject() as UITouch
    let touchLocation = touch.locationInNode(self)
    lastTouch = touchLocation


// Be sure to clear lastTouch when touches end so that the impulses stop being applies
override func touchesEnded(touches: NSSet!, withEvent event: UIEvent!) 
    lastTouch = nil


override func update(currentTime: CFTimeInterval) 
    // Only add an impulse if there's a lastTouch stored
    if let touch = lastTouch 
        let impulseVector = CGVector(touch.x - myShip.position.x, 0)
        // If myShip starts moving too fast or too slow, you can multiply impulseVector by a constant or clamp its range
        myShip.physicsBody.applyImpluse(impulseVector)
    

您可能还想在myShip.physicsBody 上使用linearDampingangularDamping 值。它们将帮助确定myShip 加速和减速的速度。

我在我的应用程序中将1.0 的值最大化:

myShip.physicsBody.linearDamping = 1.0
myShip.physicsBody.angularDamping = 1.0

如果 myShip 对您来说停止的速度不够快,您也可以尝试在您的 update 函数中应用一些中断:

override func update(currentTime: CFTimeInterval) 
    // Only add an impulse if there's a lastTouch stored
    if let touch = lastTouch 
        let impulseVector = CGVector(touch.x - myShip.position.x, 0)
        // If myShip starts moving too fast or too slow, you can multiply impulseVector by a constant or clamp its range
        myShip.physicsBody.applyImpluse(impulseVector)
     else if !myShip.physicsBody.resting 
        // Adjust the -0.5 constant accordingly
        let impulseVector = CGVector(myShip.physicsBody.velocity.dx * -0.5, 0)
        myShip.physicsBody.applyImpulse(impulseVector)
    

【讨论】:

哇,首先,非常感谢您抽出宝贵时间给出如此详细的回复!你真是太棒了!在触摸结束时将 lastTouch 更改为 nil 是天才的一击——这也是这种新语言强大功能的一个很好的例子。我卡在某事上了。出于某种原因,我在更新函数中遇到错误:“让脉冲向量:CGVector = CGVector(lastTouch.x - myShip.position.x, 0)”它说 CGPoint 没有名为 X 的成员。这是怎么回事开吗? 不客气!您在设置impulseVector 时遇到的问题是我的错字,对此我深表歉意。它应该是touch 而不是lastTouch。我编辑了我的答案并修复了它。 迈克,非常感谢!我使用触摸而不是最后一次触摸,程序运行了。这给了我一个很好的起点来调整它。几个结束的问题/想法。在第三个示例部分中,您的意思是“myShip.physicsBody.applyImpulse(impulseVector)”而不是脉冲方向吗?另外,我对“if let touch = lastTouch”语句感到困惑。如果你在 if 语句中定义 touch,它的默认值不是 nil 吗?所以 if 语句只会在 lastTouch 和 touch 为 nil 时运行。但是,您可以使用触摸来分配 x 坐标是没有意义的。为什么会有一个=? 是的,应该是impulseVector,哎呀。现在已经解决了。至于if let touch = lastTouch,它使用了一个名为optional binding 的快速功能。基本上它只会在lastTouch 不是nil 时评估为真,在这种情况下,它会将touch 设置为非nil 值。 好的,谢谢!我实际上最终使用了一个使用速度而不是冲量矢量的方程。在这种情况下,impulseVector 的问题是,每次更新都将当前向量乘以另一个向量,导致船开得太快。所以我找到了一种更简单的方法来创建移动动作:“myShip.physicsBody.velocity = CGVector(dx: (lastTouch!.x - myShip.position.x) * 16, dy: 0)” 并确保 lastTouch 是永远不会为零,这样即使我的手指离开屏幕,船也会停下来。感谢您帮助我到达这个目的地!【参考方案2】:

对于 2017 年,这是执行此处正确答案中解释的简单方法。

以前的位置不用存储,给你的...

  override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) 

    let t: UITouch = touches.first! as UITouch

    let l =      t.location(in: parent!)
    let prev =   t.previousLocation(in: parent!)

    let delta = (l - prev).vector

    physicsBody!.applyImpulse(delta)
 

就是这样。


两个音符。 (A) 正确地,您应该将 delta 距离除以 deltaTime 以获得正确的脉冲。如果你是一个业余爱好者,真的只需乘以“大约 100”就可以了。 (B) 请注意,您当然需要一个扩展或函数来将 CGPoint 转换为 CGVector,没有它就不可能做任何事情。

【讨论】:

【参考方案3】:

在您的thuchesBegantouchesMoved 中,将触摸位置存储为“目标”。然后在update 中检查您的船的位置,如果船已达到/超过目标,则将xVelocity 重置为 0。

由于您只对 x 坐标感兴趣,因此您也可以只存储 touchLocation.x。您也可以反转速度,但我认为这看起来很奇怪。请注意,如果用户再次移动手指,您的飞船将再次开始移动,因为touchMoved 将再次被触发。

顺便说一句,在touchesMoved 中,您还设置了shipLeft 属性,但在touchesBegan 中没有设置。如果此属性在其他地方使用,您应该同步它的使用。

【讨论】:

以上是关于使用 Swift + SpriteKit 将节点移动到手指的主要内容,如果未能解决你的问题,请参考以下文章

当在 Swift Spritekit 中触摸并按住屏幕时,我将如何向上移动我的节点?

SpriteKit + Swift - 如何通过缓动移动节点?

(Swift + Spritekit) - 完全删除节点及其数据

相交节点 ---- Objective-C - Swift - iOS - SpriteKit 节点

Swift SpriteKit - 防止精灵节点角旋转

Swift SpriteKit 在 x 轴上旋转节点