将SpriteNode与SpriteKit Swift3上的表面alignment

我试图alignment一个SKSpriteNode来匹配碰撞的PhysicsBody的“撞击表面”。

我正在做的是在Cube上拍摄SpriteNode。 我已经设置了碰撞和节点附件(固定关节)。 一切正常,但我需要find一种方法来旋转spriteNode匹配击中表面,你可以看到下面:

在这里输入图像说明

请注意,多维数据集可以旋转等,所以我们并不总是有一个固定的旋转值在多维数据集。

任何想法如何解决这个?

在此先感谢/马格努斯

以下是我认为会起作用的大部分答案。 我稍后会更新它。

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

然后,将磁体的zRotation与立方体的旋转进行匹配,然后根据两个粘性节点的位置将磁体与立方体alignment。

我还没有做旋转匹配或粘性alignment,但其他一切都在这里,如果你想完成它,直到我迟到:

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

好吧,所以我find了一个很好的解决scheme。 这是我做到的:

由于碰撞处理的第一步有点偏离这个范围,我们只需要说我们把3个variables从didBegin(_ contact:SKPhysicsContact)函数传递给我们自己的函数,我们把它命名为magnetHitBlock。 这是我们rest的地方。

  1. 磁铁作为精灵节点
  2. 多维数据集或块作为精灵节点。
  3. contactPoint(这是点碰撞被触发)

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

我们主要的function在做这个工作

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

所以基本上我们想要的是find立方体(或其他矩形)的接触面的angular度。 然后我们要旋转我们的磁铁来匹配那个angular度。 我们并不关心立方体的旋转,我们只是想要在空间中的接触面的angular度。

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

图。1

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

 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] 

当我们有了所有angular落的位置时,我们必须弄清楚哪个2点构成了磁铁碰到的接触表面。 而且,由于我们将存储在我们的contactPointvariables中的点击位置 ,我们可以做一些测量。

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

如果我们只是将方形的几何体作为一个立方体来工作,那么第二个最接近的点就是我们的第二个表面点,但是对于不同高度/宽度比的矩形来说并不总是这样,所以我们需要做更多的事情

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 } 

现在我们有一个存储在closestCornervariables中的所需表面点。 我们将使用它来find第二个表面点,它只能是下一个点或前一个点。

所以现在我们创build2个variablesnextCorner和prevCorner,并且相对于我们已经find的最接近的angular来设置这些点。 请注意,如果最近的点是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] } 

好,所以我们有我们最接近的angular色,这是保证我们的表面点之一。 我们有下一个angular色和PrevCroner存储。 现在我们必须弄清楚哪一个是正确的。 我们可以用两个食堂做这个。

  1. 从我们最近的angular色到下一个angular色的距离
  2. 从我们的contactPoint到下一个angular色的距离。

图3

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

好的,现在我们有了第二个表面点,我们可以使用atan2函数来获取表面的angular度,然后设置磁体。 大! 但最后一件事。 如果第二个表面点变成了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. }