简体   繁体   English

将SpriteNode与碰撞SpriteKit Swift3上的曲面对齐

[英]Align SpriteNode with surface on collision SpriteKit Swift3

Im trying to align a SKSpriteNode to match the "hit surface" of a PhysicsBody on collision. 我试图在碰撞时对齐SKSpriteNode以使其与PhysicsBody的“命中表面”匹配。

What I'm doing is to shoot the SpriteNode at a Cube. 我正在做的是在多维数据集上射击SpriteNode。 I have setup the collision and the node attachment (fixed joint). 我已经设置了碰撞和节点附件(固定关节)。 Everything works but I need to find a way to rotate the spriteNode to match the hit surface as you can see below: 一切正常,但我需要找到一种方法来旋转spriteNode以匹配命中表面,如下所示:

在此处输入图片说明

Note that the Cube can rotate etc so we don't always have a fixed rotation value on the Cube. 请注意,多维数据集可以旋转等,因此我们并不总是在多维数据集上具有固定的旋转值。

Any Ideas how to solve this? 任何想法如何解决这个问题?

Thanks in advance /Magnus 在此先感谢/ Magnus

Here is most of the answer that I think will work. 我认为这是最有效的答案。 I will update it later. 稍后再更新。

Basically, the goal is to place two sticky nodes to the magnet and to the cube respectively at the contacted point. 基本上,目标是将两个粘性节点分别放置在磁体和立方体的接触点上。

Then, you match the magnet's zRotation to the cube's rotation, and then align the magnet to the cube based on the position of the two sticky nodes. 然后,将磁体的zRotation与多维数据集的旋转相匹配,然后根据两个粘性节点的位置将磁体与多维数据集对齐。

I haven't done the rotation matching or sticky alignment, but everything else is here if you want to finish it until I get to later: 我还没有完成旋转匹配或粘性对齐,但是如果您想完成它,直到我以后再做,其他所有操作都在这里:

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

Okay so I found a solution that works good. 好的,所以我找到了一个行之有效的解决方案。 This is how I did it: 这是我的方法:

Since the first step of collision handling is a bit out side of this scope lets just say that we pass 3 variables from the didBegin(_ contact: SKPhysicsContact) function to our own function we name magnetHitBlock. 由于冲突处理的第一步不在此范围之内,所以可以说我们将didBegin(_ contact:SKPhysicsContact)函数中的3个变量传递给我们自己的函数,我们将其命名为magnetHitBlock。 This is where we do the rest. 这是剩下的事情。

  1. The Magnet as a sprite node 磁铁作为精灵节点
  2. The cube or block as a sprite node. 多维数据集或块作为sprite节点。
  3. The contactPoint (this is the point were the collision was triggered) contactPoint(这是触发碰撞的点)

called from didBegin(_ contact: SKPhysicsContact) 从didBegin致电(_联系人:SKPhysicsContact)

let magnet = contact.bodyA.node as! SKSpriteNode
let block = contact.bodyB.node as! SKSpriteNode

magnetHitBlock(magnet: magnet, attachTo: block, contactPoint: contact.contactPoint)

our main function doing the work 我们工作的主要职能

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

.....

}

So basically what we want is to find the angle of the contact surface of the cube(or other rectangular shape). 因此,基本上我们想要找到的是立方体(或其他矩形)的接触表面的角度。 And then we want to rotate our magnet to match that angle. 然后我们要旋转磁体以匹配该角度。 We don't really care about the cube rotation, we just want the angle of the contact Surface in space. 我们并不真正在乎立方体的旋转,我们只想要接触曲面在空间中的角度。

Since each surface of the cube consists of 2 points we could get the angle of the surface by using the atan2 function with these 2 points as argument. 由于立方体的每个表面都由2个点组成,我们可以使用atan2函数并将这2个点作为参数来获得表面的角度。

图。1

So first of all we need to map out all corners of the block geometry and convert these points into the scene coordinate space. 因此,首先我们需要绘制出块几何的所有角,并将这些点转换为场景坐标空间。 Then we save them in an array. 然后我们将它们保存在数组中。

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]

When we have the position of all corners we have to figure out which 2 points that makes up the contact surface that the magnet did hit. 当我们确定所有角的位置时,我们必须找出构成磁铁确实触及的接触面的哪两个点。 And since we have the hit location stored in our contactPoint variable we can do some measurements. 并且由于将命中位置存储在我们的contactPoint变量中,因此我们可以进行一些测量。

The first point is easy to find. 第一点很容易找到。 We simply check the distance from our contactPoint to all of our referencePoints (corners). 我们只需检查从contactpoint到所有参考点(角)的距离即可。 And the closest reference point is what we need because its always going to be one of the contact surface points. 而最接近的参考点正是我们所需要的,因为它始终是接触表面点之一。

( If we were only working with square geometry as a cube. the second closest point would be our second surface point. but this is not always true for a rectangle with different height/width ratio. so we need to do some more things ) 如果我们仅将正方形几何作为一个多维数据集。第二个最接近的点将是我们的第二个表面点。但是对于具有不同高/宽比的矩形,这并不总是正确的,因此我们需要做更多的事情

图2

Im sure there is a better way to write this pice of code. 我肯定有更好的方式编写这段代码。 but for now it does the trick. 但目前它可以解决问题。

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

Now we have one of the required surface points stored in closestCorner variable. 现在,我们已将所需的曲面点之一存储在mostestCorner变量中。 We will use this to find the second surface point which can only be either: the next point or the previous point. 我们将使用它来找到第二个表面点,该点只能是下一个点或上一个点。

So now we create 2 variables, nextCorner and prevCorner, and we set these points relative to the closestCorner we already found. 因此,现在我们创建2个变量,nextCorner和prevCorner,并相对于我们已经找到的最接近的角设置这些点。 Note that if the closest point is the last element in the referencePoint array, our nextCorner would have to be the first element in the array. 请注意,如果最接近的点是referencePoint数组中的最后一个元素,则nextCorner必须是数组中的第一个元素。 if the closest point is the first element in the array we have to set the prevCorner as the last element in the array: 如果最接近的点是数组中的第一个元素,我们必须将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]
    }

Alright so we have our closestCorner which is guaranteed to be one of our surface points. 好了,所以我们有最接近的角,它保证是我们的表面点之一。 And we have the nextCorner and the PrevCroner stored. 并且我们存储了nextCorner和PrevCroner。 Now we have to figure out which one of these that is correct. 现在,我们必须找出其中哪一项是正确的。 We can do this with two messurants. 我们可以用两个信使来做到这一点。

  1. Distance from our closestCorner to the nextCorner 从我们最近的角到下一角的距离
  2. Distance from our contactPoint to the nextCorner. 从我们的contactPoint到nextCorner的距离。

图3

if the distance from measurement 1 is greater then measurement 2, our second surface point has to be the nextCorner. 如果距度量1的距离大于度量2,则我们的第二个表面点必须为nextCorner。 else it has to be the prevCorner. 否则它必须是prevCorner。 And this is always the case no matter the rotation of the cube / block. 无论立方体/块的旋转如何,情况总是如此。

Okay so we now have our second surface point and we can use the atan2 function to get the angle of the surface and then set the magnet.Zrotation to this value. 好了,现在我们有了第二个表面点,可以使用atan2函数获取表面的角度,然后将magnet.Zrotation设置为此值。 Great! 大! But on last thing. 但是最后一件事。 If the second surface point turns out to be the the prevCorner instead of the nextCorner. 如果第二个表面点成为prevCorner而不是nextCorner。 The atan2 function arguments has to be reversed. atan2函数参数必须颠倒。 otherwise the rotation is going to be 180 degree out of phase. 否则旋转将异相180度。 in other words, the magnet will point outwards instead of inwards. 换句话说,磁铁将指向外部而不是向内。

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

Here is a full code example of the magnetHitBlock function: 这是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. 

}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM