使用 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
指向lastTouch
的CGVector
并将其作为脉冲应用到您的物理体。
类似:
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
上使用linearDamping
和angularDamping
值。它们将帮助确定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】:在您的thuchesBegan
和touchesMoved
中,将触摸位置存储为“目标”。然后在update
中检查您的船的位置,如果船已达到/超过目标,则将xVelocity
重置为 0。
由于您只对 x 坐标感兴趣,因此您也可以只存储 touchLocation.x
。您也可以反转速度,但我认为这看起来很奇怪。请注意,如果用户再次移动手指,您的飞船将再次开始移动,因为touchMoved
将再次被触发。
顺便说一句,在touchesMoved
中,您还设置了shipLeft
属性,但在touchesBegan
中没有设置。如果此属性在其他地方使用,您应该同步它的使用。
【讨论】:
以上是关于使用 Swift + SpriteKit 将节点移动到手指的主要内容,如果未能解决你的问题,请参考以下文章
当在 Swift Spritekit 中触摸并按住屏幕时,我将如何向上移动我的节点?
SpriteKit + Swift - 如何通过缓动移动节点?
(Swift + Spritekit) - 完全删除节点及其数据