【问题标题】:Swift 3.0 - Sprite Kit - MultitouchSwift 3.0 - Sprite Kit - 多点触控
【发布时间】:2017-07-28 23:01:05
【问题描述】:

我是 Swift SpriteKit 的新手,我想制作一个像虚拟操纵杆和两个按钮(两个节点)这样的游戏,我启用了多点触控。但是,每当我同时移动虚拟操纵杆并攻击 Spritenode 时,按钮的虚拟操纵杆似乎是锯齿状的。我如何将虚拟操纵杆的触摸与触摸攻击按钮分开

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

【问题讨论】:

  • 您遇到的问题是您没有将触摸与各种控件的行为联系起来。您可以在 touchesBegan 和 touchesMoved 调用之间存储 UITouch 对象,以便您可以跟踪哪个触摸是哪个,并分别理解它们。使用 touchesBegan 开始跟踪各种类型的触摸,并使用 touchesMoved 作为信号,表明您正在跟踪的触摸之一可能发生了变化。
  • 一个(也许)更好的方法是为您的各种按钮设置单独的“触摸板”,并让它们将游戏事件向上传递到您的游戏逻辑,而不是尝试制作一个尝试将所有可能的 UI 元素考虑在内的大项目。这样,您可以按照代码示例中显示的方式构建您的 UI 元素,但多个上下文之间不会有串扰来混淆事物。
  • 嗨,cc,非常感谢您对这个主题的回应,我已经开始在网上搜索并反复试验,但仍然没有令人满意的结果。
  • 嗨,cc,我非常感谢您对这个主题的回应,我已经开始在网上搜索并反复试验,但仍然没有令人满意的输出。当我按住操纵杆并四处移动+按住攻击按钮并四处移动时,操纵杆似乎受到按钮的“新”触摸位置的影响。有没有办法为每个按钮单独触发触摸?
  • 元,我在下面添加了一个答案,应该说明这个过程。基本上,它是关于跟踪哪个触摸与哪个功能相关 - 这是您缺少的部分。

标签: swift3 sprite-kit multi-touch touchesmoved


【解决方案1】:

您面临的问题是您正在混合触摸的上下文。这让事情变得比他们需要的更加困难和复杂。

最简单的做法是让您的虚拟操纵杆成为一个单独的 SKSpriteNode 类,该类跟踪自己的触摸并报告它们。与按钮相同 - 它们跟踪自己的触摸并报告其状态。

但是,如果您想继续使用当前的方法让高级对象跟踪多次触摸,您要做的是在 touchesBegan 中捕获每个触摸关联的上下文,然后将 touchesMoved 上的内容更新为有必要,在 touchesEnded 中取消触摸。

例如,您希望将特定的触摸与虚拟操纵杆相关联,因为如果他们将手指从操纵杆上拖到按钮上,您不希望出现奇怪的情况。并且您想知道当用户抬起手指时,哪个触摸被解除了。

这里有一些示例代码可以说明这个过程:

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

 }

在上面的代码中,我们将红框和蓝框的触摸分开了。我们总是知道哪个触摸在拖动红色框,哪个触摸在拖动蓝色框(如果有的话)。这是一个简单的示例,但它可以推广到您的情况,您可以触摸虚拟操纵杆和每个单独的按钮。

请注意,这种方法也适用于多点触控元素。如果您有一张想要缩放的地图,您可以跟踪两次触摸,以便比较它们的捏合手势。这样,如果您的捏合手势意外偏离按钮,您已经将其标记为捏合手势的一部分,并且知道不要开始触发该按钮。

但同样,更好的方法是拥有一个单独的 SKSpriteNode 子类,它只跟踪操纵杆的触摸并将其状态报告给一些更高级别的管理器类。您已经知道了执行此操作所需知道的所有内容 - 就像您没有进行所有额外检查以查看是否按下了其他按钮一样。与按钮相同。唯一的新部分是向经理“向上”发送消息,这很容易处理。

【讨论】:

  • 解释清楚。以前,我实际上已经混淆了所有的触摸。我尝试了您的编码,您基本上为每个节点分配了每次触摸。这使得触摸位置不会混淆。感谢你的帮助。我接受了你的回答。
猜你喜欢
  • 2015-02-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多