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

5

更新:我已解决问题,并找到了比提供的答案更简化的方法。我的解决方案是让宇宙飞船的速度等于它距离我的手指触摸的距离。对于更快的移动,您可以将此速度乘以一个常数。在这种情况下,我使用了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 轴上移动。当用户在屏幕上按下并保持时,我希望 SPACESHIP 能够移动到手指的 x 坐标,并且直到手指松开为止不停止向手指移动。如果 SPACESHIP 靠近用户的手指且用户的手指仍然按下,我希望它逐渐减速并停止。我还希望在 SPACESHIP 改变方向、启动和停止时应用这种平滑运动。
我正在尝试找出最好的方法来实现这一点。
到目前为止,我已经创建了节点并使其正确移动,但存在一个问题:如果我按住屏幕并保持,飞船最终将越过我的手指并继续移动。这是因为改变飞船方向的逻辑仅在我移动手指时触发。因此,将手指移到飞船上以改变飞船方向有效,但如果飞船越过我的静止手指,则不会改变方向。
我需要 SPACESHIP 节点识别出它何时越过了我的静态手指,并根据其接近程度改变方向或停止。
以下是相关代码:
第一部分:当用户按下时,找出触摸的位置并使用速度相应地移动我的飞船 (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
    }
}

第二部分 当用户移动手指时,触发一个事件来检查手指是否已经移动到了船的另一侧。如果是,改变船的方向。

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
    }

}

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

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

第四部分:更新改变船只位置的事件。

    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);

感谢阅读,期待回复!

只是为了任何在这里搜索的人,我提供了一个正确/现代的答案来回答这个老问题,希望它有所帮助。 - Fattie
3个回答

3
通过使用 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是一个天才的想法 - 这也是这种新语言强大之处的一个很好的例子。我遇到了一些问题。由于某种原因,在更新函数中出现了错误:“let impulseVector: CGVector = CGVector(lastTouch.x - myShip.position.x, 0)”它说CGPoint没有名为X的成员。发生了什么? - Tyler Brown
不客气!你在设置“impulseVector”时遇到的问题是我的笔误,很抱歉。它应该是“touch”,而不是“lastTouch”。我编辑了我的答案并修复了它。 - Mike S
Mike,非常感谢!我使用了touch而不是last touch,程序运行了。这给了我一个很好的起点来进一步调整它。还有几个问题/想法。在第三个示例部分中,你是不是指的是“myShip.physicsBody.applyImpulse(impulseVector)”而不是impulseDirection?另外,我对“if let touch = lastTouch”语句感到困惑。如果你在if语句中定义touch,它的默认值不是nil吗?所以if语句只会在lastTouch和touch都为nil时运行。但是然后用touch来分配x坐标就没有意义了。为什么只有一个=? - Tyler Brown
是的,那应该是impulseVector,糟糕。现在已经修复了。至于if let touch = lastTouch,它使用了Swift中的一个特性叫做可选绑定。基本上,只有当lastTouch不为nil时才会评估为真,并且在这种情况下,它将把touch设置为非nil值。 - Mike S
好的,谢谢!实际上我最终使用了一个使用速度而不是冲量向量的方程式。在这种情况下,使用impulseVector会导致每次更新将当前向量乘以另一个向量,从而使飞船速度过快。因此,我找到了一种更简单的方法来创建移动动作,即:"myShip.physicsBody.velocity = CGVector(dx: (lastTouch!.x - myShip.position.x) * 16, dy: 0)",并确保lastTouch永远不为空,这样即使我的手指离开屏幕,飞船也会停止。感谢您帮助我达到这个目标! - Tyler Brown

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 distance除以deltaTime以获得正确的冲量。如果你只是业余爱好者,只需乘以“大约100”即可。 (B) 请注意,当然您需要一个扩展或函数来将CGPoint转换为CGVector,否则无法完成任何操作。


-1
在你的thuchesBegantouchesMoved中将触摸位置存储为“目标”。然后在update中检查你的飞船位置,如果飞船到达/超过目标,则重置xVelocity为0。
由于你只关心x坐标,你也可以只存储touchLocation.x。你也可以反转速度,但我认为那会看起来很奇怪。请注意,如果用户再次移动手指,你的飞船将重新开始移动,因为touchMoved将再次被触发。
另外,在touchesMoved中,你还设置了shipLeft属性,但这在你的touchesBegan中没有设置。如果此属性在其他地方使用,请同步其使用。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接