Zander
Zander

Reputation: 414

Making a "Joy stick" swift

What I have been trying to do is create a "Joy stick" that moves a player around. Here is what I have so far:

import UIKit
import SpriteKit
import SceneKit

class GameViewController: UIViewController, SCNSceneRendererDelegate {

var isTracking = false
var firstTrackingLocation = CGPoint.zero
var trackingVelocity = CGPoint.zero
var trackingDistance : CGFloat = 0.0
var previousTime : NSTimeInterval = 0.0

override func viewDidLoad() {
    super.viewDidLoad()

    let scene = SCNScene(named: "art.scnassets/level.scn")!
    let scnView = self.view as! SCNView
    scnView.delegate = self
    scnView.scene = scene
    scnView.showsStatistics = true
}

override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
    if isTracking == false {
        for touch in touches {
            isTracking = true
            let location = touch.locationInView(self.view)
            firstTrackingLocation = location
        }

    }
}

override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
    if isTracking {
            trackingVelocity = touches.first!.locationInView(self.view)
    }
}

override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
    isTracking = false
    trackingVelocity = CGPoint.zero
}

func renderer(renderer: SCNSceneRenderer, updateAtTime time: NSTimeInterval) {
    if isTracking == true {

        let scnView = self.view as! SCNView
        let character = scnView.scene!.rootNode.childNodeWithName("person", recursively: true)

        let deltaTime = time - previousTime

        let pointsPerSecond: CGFloat = 1.0 * CGFloat(deltaTime)

        var xResult:CGFloat = 0.0
        var yResult:CGFloat = 0.0

        let point = firstTrackingLocation
        let endPoint = trackingVelocity

        let direction = CGPoint(x: endPoint.x - point.x, y: endPoint.y - point.y)
        if direction.x > direction.y {
            let movePerSecond = pointsPerSecond/direction.x
            xResult = direction.x*movePerSecond
            yResult = direction.y*movePerSecond

        } else {
            let movePerSecond = pointsPerSecond/direction.y
            xResult = direction.x*movePerSecond
            yResult = direction.y*movePerSecond
        }
        character!.position = SCNVector3(CGFloat(character!.position.x) + (xResult), CGFloat(character!.position.y), CGFloat(character!.position.z) + (yResult))

        let camera = scnView.scene?.rootNode.childNodeWithName("camera", recursively: true)
        camera?.position = SCNVector3(CGFloat(camera!.position.x) + (xResult), CGFloat(camera!.position.y), CGFloat(camera!.position.z) + (yResult))
    }
    previousTime = time
}

override func shouldAutorotate() -> Bool {
    return true
}

override func prefersStatusBarHidden() -> Bool {
    return true
}

override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
    return UIInterfaceOrientationMask.Landscape
}
}

Now this works except if you drag your finger to the other side of the phone the character moves 10 times faster then it would if you barely moved your finger. So what I would like to have, is a Joy stick that moves the character the same speed if you drag a little bit or to the other side of the screen. And I would also like if you changed direction at the other side of the screen that the character would move the other way. So, my guess is that there needs to be a lastPoint saved then when the touchesMoved gets called that somehow we calculate a direction from lastPoint to the currentPoint and then move the character in renderer. I understand that most of this code is probably rubbish, but thanks in advance.

Upvotes: 1

Views: 1380

Answers (3)

Russell Austin
Russell Austin

Reputation: 409

Your stated problem is the character moves at a variable rate depending on the how far from the start point the user drags their finger. The crucial point in the code seems to be this

let direction = CGPoint(x: endPoint.x - point.x, y: endPoint.y - point.y)

The difference between endPoint and point is variable and so you are getting a variable magnitude in your direction. To simplify, you could just put in a constant value like

let direction = CGPoint(x: 10, y: 10)

That gets the character moving at a constant speed when the user presses the joystick, but the character is always moving the same direction.

So somehow you've got to bracket in the variable directional values. The first thing that comes to mind is using min and max

let direction = CGPoint(x: endPoint.x - point.x, y: endPoint.y - point.y)
direction.x = min(direction.x, 10)
direction.x = max(direction.x, -10)
direction.y = min(direction.y, 10)
direction.y = max(direction.y, -10)

That seems like it would keep the magnitude of the direction values between -10 and 10. That doesn't make the speed completely constant, and it allows faster travel along diagonal lines than travel parallel to the x or y axis, but maybe it is closer to what you want.

Upvotes: 0

Knight0fDragon
Knight0fDragon

Reputation: 16827

Your joystick should be a value from 0 to 1, you need to determine the radius of your joystick, then calculate distance of (point touched to center of control) and the angle of the control with arc tan.

Now we need to ensure we never go past maxRadius, so if our distance is > maxRadius, we just set it to max radius, then we divide this value by our maxRadius to get out distance ratio.

Then we just take the cos and sin of our angle, and multiply it by our distance ratio, and get the x and y ratio values. (Should be between 0 and 1)

Finally, take this x and y value, and multiply it to the speed at which your object should be moving at.

let maxRadius = 100  //Your allowable radius
let xDist = (p2.x - p1.x)
let yDist = (p2.y - p1.y)
let distance = (sqrt((xDist * xDist) + (yDist * yDist))
let angle = atan2(yDist , xDist )
let controlDistanceRatio = (distance > maxRadius) ? 1 : distance / maxRadius
let controllerX = cos(angle) * controlDistanceRatio
let controllerY = sin(angle) * controlDistanceRatio

Upvotes: 2

Sean
Sean

Reputation: 397

This seems like a good case to use a custom UIGestureRecognizer. See Apple API Reference.

In this particular case you would create a continuous gesture. The resultant CGVector would be calculated from the center (origin) point of your joystick on screen. The recognizer would fail if the joystick node isn't selected and end if unselected (deselected). The resultant CGVector will be updated while the gesture's state is moved.

Now the tricky part to figure out would be moving the node image in such a way that allows the user to have the feeling of a joystick. For this you may need to update the node texture and make slight adjustments to the node position to give the appearance of moving a node around.

See if this helps you: Single Rotation Gesture Recognizer

Let me know if this points you in the right direction.

Upvotes: 0

Related Questions