swift 如何围绕原点以外的点正确旋转SKSpriteNodes?

sdnqo3pr  于 2023-06-21  发布在  Swift
关注(0)|答案(1)|浏览(133)

问题

我在一个GameScene中有一组SKSpriteNodes,它们以有序的方式放置,即它们的位置恒定地间隔开。(0,0),(0,150),(0,300)。我希望能够旋转所有的船只的质心的位置,并有他们的航向指向的方向,他们正在旋转。请看下面的图表来理解我的意思:

What I've Tried

我尝试将所有的精灵节点放在一个容器SKNode中,并旋转SKNode。这将导致子节点根据需要旋转,因为它们都相对于父节点定位。然而,我想避免这样做,因为我想控制每个单独的精灵节点的zRotationposition,而不是“作弊”,让一个容器节点为我做这件事。
为了解决这个问题,我尝试将标准的2D旋转矩阵应用于SKSpriteNode s的位置。下面的代码不是一个精确的副本,但它大致描述了我尝试做的事情:

@objc func sliderValueDidChange(_ sender: UISlider) { 

    // just assume I have access to the GameScene from the GameViewController
    guard let scene = view.scene as? GameScene else { return } 
    let nodes = scene.nodes 

    // prevSliderValue is the value of the slider from the previous call to 
    // this function. the slider has a range of values from 0 to 359
    let angleChange = (prevSliderValue - CGFloat(sender.value)) / 180 * .pi

    // calculating the centroid
    var cenX = CGFloat.zero
    var cenY = CGFloat.zero
    // nodes is a list of [SKSpriteNodes]
    for node in nodes {    
        cenX += node.position.x
        cenY += node.position.y
    }
    cenX = cenX / CGFloat(nodes.count)
    cenY = cenY / CGFloat(nodes.count)
    
    // applying the rotation matrix to each node's position
    for node in nodes { 
        // calculating the new position
        let newX = node.position.x * cos(angleChange) - node.position.y * sin(angleChange) 
        let newY = node.position.x * sin(angleChange) + node.position.y * cos(angleChange) 
        node.position = CGPoint(x: newX, y: newY)

        // calculating the new angle to point at 
        // let a = -atan2(node.position.y - cenY, node.position.x - cenX) + .pi/2
        // node.zRotation = a
        /*
         atan2 gives the angle relative to the X axis, but SKSpriteNodes  
         zRotation are relative to the Y axis, hence I'm adding pi/2 
         furthermore, I want a positive angle of rotation to be clockwise,
         so I'm negating the return value of the atan2 function
        */ 
    }
}

此代码的结果到目前为止尚未成功。节点大致能够沿着相对于质心的弧顺时针旋转约180 °。但是对于任何超过180º的值,节点基本上停止旋转/移动,只是在现场痉挛。尝试反转旋转(即从180º回到0º)确实会导致节点逆时针旋转,但它们不会到达原始位置(即它们的结束位置可以偏离完全垂直的几度)。此外,如果我上下拖动滑块几次,不管出于什么原因,节点最终会越来越近,直到它们会聚在质心上。
我计算最终位置的方法有问题吗?我意识到我对newXnewY的计算不是相对于质心,而是相对于原点,这让我的大脑更加混乱。我已经被困在这几天,我已经可以感觉到自己开始燃烧。任何帮助或指针将不胜感激。

编辑

我能够让它在不使用容器节点的情况下工作。代码如下:

@objc func sliderValueDidChange(_ sender: UISlider) {
        
        guard let scene = view.scene as? GameScene else { return }
        let nodes = scene.nodes
        
        let angleChange = (prevSliderVal - CGFloat(sender.value)) / 180 * .pi
        var cenX = CGFloat.zero
        var cenY = CGFloat.zero
        for node in nodes {
            cenX += node.position.x
            cenY += node.position.y
        }
        cenX = cenX / CGFloat(nodes.count)
        cenY = cenY / CGFloat(nodes.count)
        
        for node in nodes {
            let translatedPos = CGPoint(x: node.position.x - cenX,
                                        y: node.position.y - cenY)
            let newX = translatedPos.x * cos(angleChange) - translatedPos.y * sin(angleChange)
            let newY = translatedPos.x * sin(angleChange) + translatedPos.y * cos(angleChange)
            node.position = CGPoint(x: newX, y: newY)
            
            let a = CGFloat(sender.value) / 180 * .pi
            node.zRotation = -a
            
        }
        
        prevSliderVal = CGFloat(sender.value)
    }

看来我把问题想得太复杂了。为了围绕原点以外的点旋转精灵,我只需要平移节点的位置,使它们相对于质心,然后应用旋转矩阵。

lstz6jyr

lstz6jyr1#

您需要SKSpriteNodeorigin属性。原点定义旋转、平移和缩放的中心。
例如,如果你需要在左上角旋转你的精灵,你可以这样做:

let sprite = SKSpriteNode()

// top left corner
sprite.origin = CGPoint(x: 0, y: 0)
sprite.zRotation = CGFloat(90) / 180 * .pi

如果你想围绕一个点旋转一组精灵,你可以把它们作为其他空精灵的查尔兹,然后旋转父精灵:

let arrowSprites = [SKSpriteNode(imageNamed: "Arrow"), 
                    SKSpriteNode(imageNamed: "Arrow"), 
                    SKSpriteNode(imageNamed: "Arrow")]

let groupSprite = SKSpriteNode()
arrowSprites.enumerated().ForEach { (index, sprite) in

  sprite.position = CGPoint(x: 0, y: CGFloat(index) * 100)
  groupSprite.addChild(sprite)
}

// bottom center
groupSprite.origin = CGPoint(x: 0.5, y: 1)
groupSprite.zRotation = CGFloat(90) / 180 * .pi

更高级的技术(如一组精灵的群集行为)可以通过使用GameplayKit API来实现。阅读GKAgentGKBehavior,以及如何使用它来控制精灵组。

相关问题