在SpriteKit Swift3碰撞时将SpriteNode与曲面对齐

时间:2017-07-08 12:51:28

标签: ios swift sprite-kit

我试图将SKSpriteNode对齐以匹配PhysicsBody在碰撞时的“命中表面”。

我正在做的是在立方体上拍摄SpriteNode。我已经设置了碰撞和节点附件(固定接头)。 一切正常但我需要找到一种旋转spriteNode以匹配命中曲面的方法,如下所示:

enter image description here

请注意,立方体可以旋转等,因此我们并不总是在立方体上有固定的旋转值。

任何想法如何解决这个问题?

提前致谢 /马格努斯

2 个答案:

答案 0 :(得分:0)

以下是我认为可行的大部分答案。我稍后会更新。

基本上,目标是在接触点分别将两个粘性节点放置到磁铁和立方体上。

然后,将磁铁的zRotation与立方体的旋转相匹配,然后根据两个粘性节点的位置将磁铁与立方体对齐。

我还没有完成旋转匹配或粘贴对齐,但是如果你想要完成它,那么其他一切都在这里,直到我以后:

// Props:
class GameScene: SKScene, SKPhysicsContactDelegate {

  struct Category {
    static let
    border = UInt32(2),
    cube = UInt32(4),
    magnet = UInt32(8)
  }

  let names = (border: "border", cube: "cube", magnet: "magnet", stickyPoint: "stickyPoint")

  var flag_hitThisSimulation = false
}


// Setup:
extension GameScene {

  private func setupNodes() {
    border: do {
      let pb = SKPhysicsBody(edgeLoopFrom: frame)
      pb.categoryBitMask = Category.border
      physicsBody = pb
    }
    cube: do {
      let cubeNode = SKSpriteNode(color: .blue, size: CGSize(width: 150, height: 150))
      let pb = SKPhysicsBody(rectangleOf: cubeNode.size)
      pb.affectedByGravity = false
      pb.isDynamic = false
      pb.categoryBitMask = Category.cube
      pb.contactTestBitMask = Category.magnet
      cubeNode.physicsBody = pb
      cubeNode.position.y += 200
      cubeNode.name = names.cube
      cubeNode.run(.repeatForever(.rotate(byAngle: 3.14, duration: 3)))
      addChild(cubeNode)
    }
    magnet: do {
      let magnetNode = SKSpriteNode(color: .green, size: CGSize(width: 50, height: 12))
      let pb = SKPhysicsBody(rectangleOf: magnetNode.size)
      pb.categoryBitMask = Category.magnet
      pb.affectedByGravity = false
      magnetNode.physicsBody = pb
      magnetNode.name = names.magnet
      addChild(magnetNode)
    }
  }

  override func didMove(to view: SKView) {

    removeAllChildren()
    physicsWorld.contactDelegate = self
    setupNodes()

  }
}

// Physics:
extension GameScene {

  private func assignNodeOfName(_ name: String, contact: SKPhysicsContact) -> SKNode? {
    guard let nodeA = contact.bodyA.node, let nodeB = contact.bodyB.node else { fatalError("how are there no nodes?") }
    if      nodeA.name == name { return nodeA }
    else if nodeB.name == name { return nodeB }
    else                       { return nil   }
  }

  private func addNodeToPoint(_ point: CGPoint, parent: SKNode) {
    let node = SKSpriteNode(color: .orange, size: CGSize(width: 5, height: 5))
    node.position = point
    node.name = names.stickyPoint
    parent.addChild(node)
  }

  func alignMagnetToCube() {

  }

  func didBegin(_ contact: SKPhysicsContact) {
    defer { flag_hitThisSimulation = true }
    if flag_hitThisSimulation { return }

    let contactedNodes = contact.bodyA.categoryBitMask + contact.bodyB.categoryBitMask

    switch contactedNodes {

    case Category.magnet + Category.cube:
      // Place the two sticky nodes:
      let cube   = assignNodeOfName(names.cube,   contact: contact)!
      let magnet = assignNodeOfName(names.magnet, contact: contact)!

      let cubePoint   = convert(contact.contactPoint, to: cube)
      let magnetPoint = convert(contact.contactPoint, to: magnet)

      addNodeToPoint(cubePoint,   parent: cube)
      addNodeToPoint(magnetPoint, parent: magnet)

      // Set the magnet's zRotation to the cube's zRotation, then align the two stickyNodes:
      // fluidity.SLEEPY(for: now)
      // finish.later()

    default: ()
    }
  }
}

// Game loop:
extension GameScene {

  // Change to touchesBegan for iOS:
  override func mouseDown(with event: NSEvent) {

    let magnet = childNode(withName: names.magnet) as! SKSpriteNode

    // Start simulation:
    magnet.removeAllActions()
    magnet.physicsBody!.applyImpulse(CGVector(dx: 0, dy: 25))

    // End simulation:
    magnet.run(.wait(forDuration: 2)) {
      print("resetting simulation! \(event.timestamp)")
      magnet.physicsBody!.velocity = CGVector.zero
      magnet.zRotation = 0 // FIXME: This isn't working..
      magnet.position = CGPoint.zero
      self.flag_hitThisSimulation = false

      // Remove sticky nodes:
      for node in self.children {
        for childNode in node.children {
          if childNode.name == self.names.stickyPoint { childNode.removeFromParent() }
        }
      }
    }
  }
}

答案 1 :(得分:0)

好的,我找到了一个效果很好的解决方案。我就这样做了:

由于碰撞处理的第一步是这个范围的一点,我们只想说我们将didBegin(_ contact:SKPhysicsContact)函数中的3个变量传递给我们自己的函数,我们将其命名为magnetHitBlock。这是我们完成剩下的工作。

  1. 磁铁作为精灵节点
  2. 将多维数据集或块作为精灵节点。
  3. contactPoint(这是触发碰撞的重点)
  4. 从didBegin调用(_ contact:SKPhysicsContact)

    let magnet = contact.bodyA.node as! SKSpriteNode
    let block = contact.bodyB.node as! SKSpriteNode
    
    magnetHitBlock(magnet: magnet, attachTo: block, contactPoint: contact.contactPoint)
    

    我们的主要功能是做这项工作

    func magnetHitBlock(magnet:SKSpriteNode, attachTo block:SKSpriteNode, contactPoint:CGPoint){
    
    .....
    
    }
    

    基本上我们想要的是找到立方体(或其他矩形)的接触面的角度。然后我们想要旋转磁铁以匹配该角度。我们并不关心立方体的旋转,我们只想要在空间中接触Surface的角度。

    由于立方体的每个表面由2个点组成,我们可以通过使用atan2函数得到表面的角度,并将这2个点作为参数。

    Fig 1

    首先,我们需要绘制块几何体的所有角,并将这些点转换为场景坐标空间。然后我们将它们保存在一个数组中。

    let topLeft = convert(CGPoint(x: -block.size.width/2, y: block.size.height/2), from: block)
    let bottomLeft = convert(CGPoint(x: -block.size.width/2, y: -block.size.width/2), from: block)
    let topRight = convert(CGPoint(x: block.size.width/2, y: block.size.height/2), from: block)
    let bottomRight = convert(CGPoint(x: block.size.width/2, y: -block.size.width/2), from: block)
    
    let referencePoints = [topLeft,topRight,bottomRight,bottomLeft]
    

    当我们拥有所有角落的位置时,我们必须弄清楚哪个2点组成了磁铁确实碰到的接触面。由于我们的contactPoint变量中存储了命中位置,我们可以进行一些测量。

    第一点很容易找到。我们只需检查从contactPoint到所有referencePoints(角落)的距离。最接近的参考点是我们需要的,因为它始终是接触面点之一。

    如果我们只使用方形几何作为立方体。第二个最接近的点将是我们的第二个表面点。但对于具有不同高度/宽度比的矩形,情况并非总是如此。所以我们需要做更多的事情

    fig2

    我确信有更好的方法来编写这些代码。但是现在它可以解决问题。

        //Varible to store the closetCorner - Default topLeft
        var closestCorner = referencePoints[0]
    
        //We set the prevDistance to something very large.
        var prevDistance:CGFloat = 10000000
    
        for corner in referencePoints{
            // We check the distance from the contactPoint to each corner.
            // If the distance is shorter then the last checked corner we update the closestCorner varible and also the prevDistance.
            let distance = hypot(corner.x - contactPoint.x, corner.y - contactPoint.y)
            if distance < prevDistance{
                prevDistance = distance
                closestCorner = corner
            }
    

    现在我们在nearestCorner变量中存储了一个必需的曲面点。我们将使用它来找到第二个表面点,它只能是:下一个点或前一个点。

    所以现在我们创建了2个变量nextCorner和prevCorner,并且我们相对于我们已经找到的nearestCorner设置了这些点。 请注意,如果最近的点是referencePoint数组中的最后一个元素,则nextCorner必须是数组中的第一个元素。如果最近的点是数组中的第一个元素,我们必须将prevCorner设置为数组中的最后一个元素:

        var nextCorner:CGPoint
        var prevCorner:CGPoint
        let index = referencePoints.index(of: closestCorner)
    
        if index == 3{
            nextCorner = referencePoints[0]
        }
        else{
            nextCorner = referencePoints[index! + 1]
        }
    
        if index == 0{
    
            prevCorner = referencePoints[3]
    
        }
        else{
            prevCorner = referencePoints[index! - 1]
        }
    

    好的,所以我们有最近的角度,保证是我们的表面点之一。我们存储了nextCorner和PrevCroner。现在我们必须弄清楚哪一个是正确的。我们可以用两个消息来做到这一点。

    1. 距离nearestCorner到nextCorner的距离
    2. 从我们的contactPoint到nextCorner的距离。
    3. fig 3

      如果测量1的距离大于测量2,则我们的第二个表面点必须是nextCorner。否则它必须是prevCorner。无论立方体/块的旋转如何,情况总是如此。

      好的,我们现在有第二个表面点,我们可以使用atan2函数来获取曲面的角度,然后将磁铁设置为此值。大!但最后一件事。如果第二个表面点是prevCorner而不是nextCorner。必须颠倒atan2函数参数。否则旋转将是180度异相。换句话说,磁铁将指向外而不是向内。

          // Distance from closestCorner to nextCorner.
          let distToNextCorner = hypot(closestCorner.x - nextCorner.x, closestCorner.y - nextCorner.y)
          // Distance from contactPoint to nextCorner
          let distFromContactPoint = hypot(contactPoint.x - nextCorner.x, contactPoint.y - nextCorner.y)
      
          let firstSurfacePoint = closestCorner
          var secondSurfacePoint:CGPoint
      
          if distToNextCorner > distFromContactPoint{
      
              secondSurfacePoint = nextCorner
      
              let angle = atan2( firstSurfacePoint.y - secondSurfacePoint.y  ,  firstSurfacePoint.x - secondSurfacePoint.x )
              magnet.zRotation = angle
      
          }
          else{
              secondSurfacePoint = prevCorner
      
              let angle = atan2(secondSurfacePoint.y - firstSurfacePoint.y , secondSurfacePoint.x - firstSurfacePoint.x )
              magnet.zRotation = angle
          }
      

      以下是magnetHitBlock函数的完整代码示例:

      func magnetHitBlock(magnet:SKSpriteNode, attachTo block:SKSpriteNode, contactPoint:CGPoint){
      
      
          // first move the magnet to the contact point.
          magnet.position = contactPoint
      
      
          // find the corners and convert thoes points into the scene coordinate space
          let topLeft = convert(CGPoint(x: -block.size.width/2, y: block.size.height/2), from: block)
          let bottomLeft = convert(CGPoint(x: -block.size.width/2, y: -block.size.width/2), from: block)
          let topRight = convert(CGPoint(x: block.size.width/2, y: block.size.height/2), from: block)
          let bottomRight = convert(CGPoint(x: block.size.width/2, y: -block.size.width/2), from: block)
      
          // Then we put these "referencePoints" into an array for easy acces.
          // Note that we go in a clockwise direction from the top left
      
          let referencePoints = [topLeft,topRight,bottomRight,bottomLeft]
      
      
          // Find the closest corner.
      
          // Varible to store the closetCorner
          var closestCorner = referencePoints[0]
      
          //We set the prevDistance to something very large.
          var prevDistance:CGFloat = 10000000
      
          for corner in referencePoints{
      
              // We check the distance from the contactPoint to each corner.
              // If the distance is smaler then the last checked corner we update the closestCorner varible and also the prevDistance.
              let distance = hypot(corner.x - contactPoint.x, corner.y - contactPoint.y)
              if distance < prevDistance{
                  prevDistance = distance
                  closestCorner = corner
              }
      
          }
      
      
      
          // Now lets find the NextCorner and prevCorner relative to the closestCorner.
          var nextCorner:CGPoint
          var prevCorner:CGPoint
          let index = referencePoints.index(of: closestCorner)
      
          if index == 3{
              nextCorner = referencePoints[0]
          }
          else{
              nextCorner = referencePoints[index! + 1]
          }
      
          if index == 0{
      
              prevCorner = referencePoints[3]
      
          }
          else{
              prevCorner = referencePoints[index! - 1]
          }
      
      
      
          // Distance from closestCorner to nextCorner.
          let distToNextCorner = hypot(closestCorner.x - nextCorner.x, closestCorner.y - nextCorner.y)
          // Distance from contactPoint to nextCorner
          let distFromContactPoint = hypot(contactPoint.x - nextCorner.x, contactPoint.y - nextCorner.y)
      
      
      
          let firstSurfacePoint = closestCorner
          var secondSurfacePoint:CGPoint
      
          if distToNextCorner > distFromContactPoint{
      
              secondSurfacePoint = nextCorner
      
              let angle = atan2( firstSurfacePoint.y - secondSurfacePoint.y  ,  firstSurfacePoint.x - secondSurfacePoint.x )
              magnet.zRotation = angle
      
          }
          else{
              secondSurfacePoint = prevCorner
      
              let angle = atan2(secondSurfacePoint.y - firstSurfacePoint.y , secondSurfacePoint.x - firstSurfacePoint.x )
              magnet.zRotation = angle
      
      
          }
      
      
          // currently the magnet position is centered on the block border. lets position it edge to edge with the block.
      
          magnet.position = convert(CGPoint(x: 0, y: -magnet.size.height/2), from: magnet)
      
         // Add code to attach the magnet to the block. 
      
      }