移动触摸旋转精灵节点

3

我刚接触Swift和SpriteKit,正在学习如何理解游戏“Fish & Trip”中的控制方式。精灵节点始终位于视图中心,并且会根据您的触摸移动而旋转,无论您在哪里触摸并移动(按住),它都会相应地旋转。

这里的难点在于它与图片1和2中所述的平移手势和简单的触摸位置不同。

enter image description here

对于第一张图片,触摸位置经过atan2f处理,然后发送到SKAction.rotate,就完成了,我可以做到这一点。

对于第二张图片,我可以通过设置UIPanGestureRecognizer来实现,但是只有在您围绕初始点(touchesBegan)移动手指时才能旋转节点。

我的问题是针对第三张图片,也就是Fish & Trip游戏中的情况,您可以在屏幕上的任何位置触摸,然后移动(按住)到任何位置,节点仍将随着您的移动而旋转,您不必围绕初始点移动手指来使节点旋转,而且旋转是平滑和准确的。

我的代码如下,它效果不太好,有些抖动,我的问题是如何更好地实现这一点?如何使旋转平滑?

是否有一种方法可以在touchesMoved函数中过滤previousLocation?当我使用此属性时,我总是遇到抖动问题,我认为它报告得太快了。当我使用UIPanGestureRecoginzer时,我没有遇到任何问题,而且非常平滑,所以我想我一定在previousLocation上做错了什么。

func mtoRad(x: CGFloat, y: CGFloat) -> CGFloat {

    let Radian3 = atan2f(Float(y), Float(x))

    return CGFloat(Radian3)  
}

func moveplayer(radian: CGFloat){

    let rotateaction = SKAction.rotate(toAngle: radian, duration: 0.1, shortestUnitArc: true)

    thePlayer.run(rotateaction)
}

var touchpoint = CGPoint.zero
var R2 : CGFloat? = 0.0

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

    for t in touches{

        let previousPointOfTouch = t.previousLocation(in: self)

        touchpoint = t.location(in: self)

        if touchpoint.x != previousPointOfTouch.x && touchpoint.y != previousPointOfTouch.y {

            let delta_y = touchpoint.y - previousPointOfTouch.y
            let delta_x = touchpoint.x - previousPointOfTouch.x

            let R1 = mtoRad(x: delta_x, y: delta_y)

            if R2! != R1 { 
                moveplayer(radiant: R1)
            }

            R2 = R1
        }             
    }
}

我不熟悉这个游戏,旋转是通过一个手指还是两个手指完成的?我正在尝试理解这里的第三张图片,但我不知道你如何区分平移和旋转。 - Knight0fDragon
旋转由1个手指完成。对于第3张图片,我的意思是您不需要在初始触点周围移动手指以进行完整的旋转,因为要使用pan来完成旋转,您需要在初始位置周围滑动手指,因为“PanGestureRecognizer”计算的是初始点和当前平移位置之间的差异,而不是当前位置和上一个位置之间的差异。 - user34232
哦,我明白了,虚线不是你在平移,而是你在缩放操作。 - Knight0fDragon
你的程序出现口吃是因为你在开始下一个旋转动作之前没有取消上一个旋转动作。你需要设置一个队列来在上一个动作完成后触发新的动作,或者取消上一个动作并包含任何未完成的旋转在新的弧形中。 - Knight0fDragon
虚线是平移而不是缩放,这是我移动手指的方式,圆圈是我的手指第一次触摸屏幕的位置,然后沿着虚线继续移动。不取消之前的操作可能是情况,让我看看它是否有效。 - user34232
1个回答

3

这并不是一个答案(但我希望稍后能够发布一个/编辑成一个),但您可以通过更改movePlayer()的定义,使您的代码更加“Swifty”,从而使内容更加通俗易懂:

func moveplayer(radian: CGFloat)

to

rotatePlayerTo(angle targetAngle: CGFloat) {
   let rotateaction = SKAction.rotate(toAngle: targetAngle, duration: 0.1, shortestUnitArc: true)
   thePlayer.run(rotateaction)
}

那么,要调用它,而不是:

moveplayer(radiant: R1)

使用

rotatePlayerTo(angle: R1)

这段话中提到的“更可读”是因为它更好地描述了您正在做什么。

此外,您旋转到新角度的速度保持不变,约为0.1秒-因此,如果玩家需要旋转更远,它将旋转得更快。最好保持旋转速度恒定(以弧度每秒为单位)。我们可以按如下方式实现:

添加以下属性:

let playerRotationSpeed = CGFloat((2 *Double.pi) / 2.0) //Radian per second; 2 second for full rotation

将您的moveShip修改为:

func rotatePlayerTo(angle targetAngle: CGFloat) {
    let angleToRotateBy = abs(targetAngle - thePlayer.zRotation)
    let rotationTime = TimeInterval(angleToRotateBy / shipRotationSpeed)
    let rotateAction = SKAction.rotate(toAngle: targetAngle, duration: rotationTime , shortestUnitArc: true)
    thePlayer.run(rotateAction)
}

这可能有助于使旋转更加平稳。

谢谢你的代码,我刚试了一下,它确实旋转得更平滑、更慢了,但问题仍然存在。我在想问题可能出在 touchedMoved 函数上,似乎 previousLocation 报告得太快了,有没有办法对其进行一些过滤,并让它像 UIPanGestureRecognizer 中的翻译一样行为? - user34232
有点棘手 - 这是一种不错的、平滑的控制方法。我还没有时间尝试你的代码或复制游戏。它似乎与 Slitherio 中使用的相同。 - Steve Ives

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