3
votes

I am new to Swift and SpriteKit and am learning to understand the control in the game "Fish & Trip". The sprite node is always at the center of the view and it will rotate according to moving your touch, no matter where you touch and move (hold) it will rotate correspondingly.

The difficulty here is that it is different from the Pan Gesture and simple touch location as I noted in the picture 1 and 2.

enter image description here

For the 1st pic, the touch location is processed by atan2f and then sent to SKAction.rotate and it is done, I can make this working.

For the 2nd pic, I can get this by setup a UIPanGestureRecognizer and it works, but you can only rotate the node when you move your finger around the initial point (touchesBegan).

My question is for the 3rd pic, which is the same as the Fish & Trip game, you can touch anywhere on the screen and then move (hold) to anywhere and the node still rotate as you move, you don't have to move your finger around the initial point to let the node rotate and the rotation is smooth and accurate.

My code is as follow, it doesn't work very well and it is with some jittering, my question is how can I implement this in a better way? and How can I make the rotation smooth?

Is there a way to filter the previousLocation in the touchesMoved function? I always encountered jittering when I use this property, I think it reports too fast. I haven't had any issue when I used UIPanGestureRecoginzer and it is very smooth, so I guess I must did something wrong with the 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
        }             
    }
}
1
I am not familiar with this game, is rotation accomplished by 1 finger or 2? I am trying to understand pic 3 here, but I do not know how you are distinguishing a pan from a rotationKnight0fDragon
Rotation is accomplished by 1 finger. For the Pic 3, I mean you don't need to pan around the initial touchpoint to make a full rotation, since in order to accomplish a rotation using pan, you need to pan your finger around the initial position you touched because "PanGestureRecognizer" calculates the difference between the initial point and the current panned position instead of the current position and previous position.user34232
oh ok I got you, the dotted line is not you panning, the dotted line is you zooming the actionKnight0fDragon
You are stuttering because you are not cancelling the previous rotation action before starting the next. You need to either set up a queue to fire off actions when the previous action completes, or cancel the previous action and include any missed rotation in your new arcKnight0fDragon
The dotted line is panning, not zooming, it is how I move my finger, the circle is where my finger touch screen for the first time, and then I keep moving along the dotted line. Not cancelling the previous action may be the case, let me see if it works.user34232

1 Answers

3
votes

This is not an answer (yet - hoping to post one/edit this into one later), but you can make your code a bit more 'Swifty' by changing the definition for movePlayer() from:

func moveplayer(radian: CGFloat)

to

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

then, to call it, instead of:

moveplayer(radiant: R1)

use

rotatePlayerTo(angle: R1)

which is more readable as it describes what you are doing better.

Also, your rotation to the new angle is constant at 0.1s - so if the player has to rotate further, it will rotate faster. it would be better to keep the rotational speed constant (in terms of radians per second). we can do this as follows:

Add the following property:

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

change your moveShip to:

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

this may help smooth the rotation too.