Search code examples
swift3sprite-kitmulti-touchtouchesmoved

Swift 3.0 - Sprite Kit - Multitouch


I'm new to Swift SpriteKit, I want to make a game like a virtual joystick and two buttons(two nodes), I've enabled the multi-touch. However, whenever I move both virtual joystick and attack Spritenode, the virtual joystick of the button seems to be Jagging. How am I gonna separate the touches of virtual joystick from touches attackbutton

class GameScene: SKScene {

var defend : Bool = false
var attack : Bool = false
var stickMove : Bool = false
var stickEnd:Bool = false
var moveattack:Bool = false
var movedefend:Bool = false

var playermovement:Bool = true

let vj1 = SKSpriteNode(imageNamed: "vj1")
let vj2 = SKSpriteNode(imageNamed: "vj2")
let player = SKSpriteNode(imageNamed: "player")
let rotationSpeed :CGFloat = CGFloat(M_PI)
let rotationOffSet : CGFloat = -CGFloat(M_PI/2.0)
let attackvj = SKSpriteNode(imageNamed: "attackvj")
let defendvj = SKSpriteNode(imageNamed: "defendvj")


private var touchPosition: CGFloat = 0
private var targetZRotation: CGFloat = 0

override func didMove(to view: SKView) {
    self.view?.isMultipleTouchEnabled = true
    self.backgroundColor = SKColor.black

    //position of joystick
    vj1.zPosition = 1
    vj1.xScale = 1.5
    vj1.yScale = 1.5
    self.addChild(vj1)


    vj1.position = CGPoint(x: self.size.width*15/100, y:self.size.height*30/100)

    vj2.zPosition = 1
    vj2.xScale = 1.5
    vj2.yScale = 1.5
    self.addChild(vj2)


    vj2.position = vj1.position

    player.zPosition = 0
    player.physicsBody = SKPhysicsBody(rectangleOf: player.size)
    player.physicsBody!.affectedByGravity = false
    player.position = CGPoint(x: self.size.width/2, y:self.size.height/2)
    self.addChild(player)

    attackvj.anchorPoint = CGPoint(x: 0.5, y:0.5)
    attackvj.position = CGPoint(x: self.size.width*80/100, y:self.size.height*30/100)
    attackvj.xScale = 2.0
    attackvj.yScale = 2.0
    self.addChild(attackvj)

    defendvj.anchorPoint = CGPoint(x: 0.5, y:0.5)
    defendvj.position = CGPoint(x: self.size.width*90/100, y:self.size.height*50/100)
    defendvj.xScale = 2.0
    defendvj.yScale = 2.0

    self.addChild(defendvj)


    vj1.alpha = 0.4
    vj2.alpha = 0.4
    attackvj.alpha = 0.4
    defendvj.alpha = 0.4

}


override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    for touch in (touches){
        let location = touch.location(in: self)

        if vj2.contains(location){

            stickEnd = false
            stickMove = true
        }

        if defendvj.contains(location){
            defend = true
        }


        if attackvj.contains(location){
            attack = true
            attackvj.xScale = 2.5
            attackvj.yScale = 2.5

        }

        if(stickMove == true && attack == true){
            moveattack = true
        }
        if(stickMove == true && defend == true){

            movedefend = true
        }

    }
}

override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
    for touch in (touches){
        let location = touch.location(in: self)
        let previousLocation = touch.previousLocation(in: self)
        let v = CGVector(dx: location.x - vj1.position.x, dy: location.y - vj1.position.y)
        print("locationsss" , location , "previouslocationsss", previousLocation)
        let angle = atan2(v.dy, v.dx)
        targetZRotation = angle + rotationOffSet
        let length:CGFloat = vj1.frame.size.height / 2

        let xDist:CGFloat = sin(angle - 1.57079633) * length
        let yDist:CGFloat = cos(angle - 1.57079633) * length
        if(stickMove == true){

            if(vj1.frame.contains(location)){

                vj2.position = location
            }
            else{

                vj2.position = CGPoint(x: vj1.position.x - xDist, y: vj1.position.y + yDist)
            }
            if(attackvj.frame.contains(location)){//How am I gonna make this location in attackvj, not to influence my joystick location?


            }

        }
    }
}

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

    if(stickMove == true && attack == false && defend == false){
        let move:SKAction = SKAction.move(to: vj1.position, duration: 0.2)
        move.timingMode = .easeOut
        vj2.run(move)
        stickEnd = true
        stickMove = false
    }
    if(attack == true){
        attack = false
        attackvj.xScale = 2.0
        attackvj.yScale = 2.0
        moveattack = false
    }
    if(defend == true){
        defend = false
        movedefend = false
    }
}


override func update(_ currentTime: TimeInterval) {
    //rotation
    if (stickEnd == false) {
    var angularDisplacement = targetZRotation - player.zRotation
    if angularDisplacement > CGFloat(M_PI) {
        angularDisplacement = (angularDisplacement - CGFloat(M_PI)*2)
    }
    else if angularDisplacement < -CGFloat(M_PI) {
        angularDisplacement = (angularDisplacement + CGFloat(M_PI)*2)
    }

    if abs(angularDisplacement) > rotationSpeed*(1.0/60.0){
        let angularVelocity = angularDisplacement < 0 ? -rotationSpeed : rotationSpeed
        player.physicsBody!.angularVelocity = angularVelocity

    } else {
        player.physicsBody!.angularVelocity = 0

        player.zPosition = targetZRotation
    }


    }
    else{
        player.physicsBody!.angularVelocity = 0
    }
    //movement but use attack button to testing
    if (attack == true)
    {
        player.position = CGPoint(x:player.position.x + cos(player.zRotation + 1.57079633),y:player.position.y + sin(player.zRotation + 1.57079633))
    }    
}

Solution

  • The problem you are facing is that you are mixing the contexts for your touches. This is making things more difficult and complicated than they need to be.

    The easiest thing to do would be to make your virtual joystick a separate SKSpriteNode class that tracks its own touches and reports them. Same with the buttons - they track their own touches and report their state.

    But if you want to continue with your current approach of having a high-level object track multiple touches, what you want to do is capture the context that each touch is associated with in touchesBegan, and then just update things on touchesMoved as necessary, canceling the touches in touchesEnded.

    For instance, you want to associate a particular touch with the virtual joystick, because you don't want weirdness if they drag their finger off of it and over to the button, say. And you want to know exactly which touch is lifted off when the user lifts a finger.

    Here's some sample code that should illustrate the process:

     //
     // This scene lets the user drag a red and a blue box
     // around the scene.  In the .sks file (or in the didMove
     // function), add two sprites and name them "red" and "blue".
     //
    
     import SpriteKit
     import GameplayKit
    
     class GameScene: SKScene {
    
        private var redTouch:UITouch?
        private var blueTouch:UITouch?
    
         override func didMove(to view: SKView) {
            super.didMove(to: view)
            isUserInteractionEnabled = true
         }
    
        override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    
            //  Grab some references to the red and blue sprites.
            //  (They must be direct children of the scene, or the
            //  paths must be amended.)
            guard let redBox = childNode(withName: "red") else { return }
            guard let blueBox = childNode(withName: "blue") else { return }
    
            for touch in touches {
                //  Get the location of the touch in SpriteKit Scene space.
                let touchLocation = touch.location(in: self)
                //  Check to see if the user is touching one of the boxes.
                if redBox.contains( touchLocation ) {
                    //  If we already have a touch in the red box, do nothing.
                    //  Otherwise, make this our new red touch.
                    redTouch = touch
                } else if blueBox.contains( touchLocation ) {
                    //  If we already have a touch in the blue box, do nothing.
                    //  Otherwise, make this our new blue touch.
                    blueTouch = touch
                }
            }
    
        }
    
        override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
            //  We have already established which touches are active,
            //  and we have already tied them to the two contexts, so
            //  we just need to read their current location and update
            //  the location of the red and blue boxes for the touches
            //  that are active.
            if let redTouch = redTouch {
                guard let redBox = childNode(withName: "red") else { return }
                let location = redTouch.location(in:self)
                redBox.position = location
            }
            if let blueTouch = blueTouch {
                guard let blueBox = childNode(withName: "blue") else { return }
                let location = blueTouch.location(in:self)
                blueBox.position = location
            }
        }
    
        override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
            //  The parameter touches contains a list of ending touches,
            //  so we check the touches we are currently tracking to
            //  see if they are newly lifted.  If so, we cancel them.
            if let touch = redTouch {
                if touches.contains( touch ) {
                    redTouch = nil
                }
            }
            if let touch = blueTouch {
                if touches.contains( touch ) {
                    blueTouch = nil
                }
            }
        }
    
     }
    

    In the above code, we have separated out the touches on the red box and the blue box. We always know which touch is dragging the red box around and which touch is dragging the blue box around, if any. This is a simple example, but it's generalizable to your situation, where you'd have touches for the virtual joystick and each individual button.

    Note that this approach works well for multitouch elements, too. If you have a map that you want to be zoomable, you can keep track of two touches so that you can compare them for pinch gestures. That way, if your pinch gesture accidentally strays over a button, you've already marked it as part of the pinch gesture, and know not to start triggering that button.

    But again, a better approach would be to have a separate SKSpriteNode subclass that just tracks the joystick touches and reports its state to some higher-level manager class. You already know everything you need to know to do this - it's like what you have without all the extra checking to see if there are other buttons pressed. Same with the buttons. The only new part would be messaging "up the chain" to a manager, and that's pretty straightforward to deal with.