我试图将SKSpriteNode对齐以匹配PhysicsBody在碰撞时的“命中表面”。
我正在做的是在立方体上拍摄SpriteNode。我已经设置了碰撞和节点附件(固定接头)。 一切正常但我需要找到一种旋转spriteNode以匹配命中曲面的方法,如下所示:
请注意,立方体可以旋转等,因此我们并不总是在立方体上有固定的旋转值。
任何想法如何解决这个问题?
提前致谢 /马格努斯
答案 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。这是我们完成剩下的工作。
从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个点作为参数。
首先,我们需要绘制块几何体的所有角,并将这些点转换为场景坐标空间。然后我们将它们保存在一个数组中。
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(角落)的距离。最接近的参考点是我们需要的,因为它始终是接触面点之一。
(如果我们只使用方形几何作为立方体。第二个最接近的点将是我们的第二个表面点。但对于具有不同高度/宽度比的矩形,情况并非总是如此。所以我们需要做更多的事情)
我确信有更好的方法来编写这些代码。但是现在它可以解决问题。
//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的距离大于测量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.
}