如何设置圆的物理属性使其遵循给定路径

how to set physics properties for a circle so it follows given path

物理体圈的运动对于我想要实现的目标来说太不稳定了。我想对其进行限制,使其遵循特定路径接触特定点(或一系列点),如下图所示。如何设置物理属性以遍历相似的路径?

使用followPath:duration:

快速实施
override func didMoveToView(view: SKView) {
    self.backgroundColor = UIColor.blackColor()
    let xWidth: CGFloat = CGRectGetMaxX(self.frame)
    let yHeight: CGFloat = CGRectGetMaxY(self.frame)

    let ball = SKSpriteNode(color: UIColor.redColor(), size: CGSizeMake(50.0, 50.0))
    let offset : CGFloat = ball.size.width / 2

    // Moving path
    let path = CGPathCreateMutable()
    CGPathMoveToPoint(path, nil, offset, yHeight / 2)
    CGPathAddLineToPoint(path, nil, xWidth * 2 / 3, yHeight - offset)
    CGPathAddLineToPoint(path, nil, xWidth - offset, yHeight * 2 / 3)
    CGPathAddLineToPoint(path, nil, xWidth / 2, offset)
    CGPathAddLineToPoint(path, nil, offset, yHeight / 2)

    // Movement
    let moveByPath = SKAction.followPath(path, asOffset: false, orientToPath: false, duration: 4.0)
    let moveForever = SKAction.repeatActionForever(moveByPath)
    ball.runAction(moveForever)

    self.addChild(ball)
}

GameViewController.swift 中,我将默认的 GameScene.unarchiveFromFile 方法更改为 let scene = GameScene(size: view.bounds.size) 以创建场景大小。

预览:

how to set physics properties for a circle so it follows given path

所以本质上,您希望使用 real-time 运动将节点移动到特定点。我有一个答案 here 展示了如何做到这一点,但是考虑到这个问题收到的赞成票数,我将提供更详细的答案。

我链接到的答案没有提供遍历点的路径。因此,下面我提供了一个解决方案,展示了如何在下面完成此操作。它只是简单地移动到路径中的每个点,每次节点到达一个点时,我们都会增加索引以移动到下一个点。我还为行进速度、速率(使运动更平滑或更静态)以及节点是否应重复路径添加了一些变量。您可以进一步扩展此解决方案以更好地满足您的游戏需求。我肯定会考虑子类化一个节点并将这种行为构建到其中,这样你就可以 re-use 多个节点的这个动作。

最后一点,您可能会注意到我下面的解决方案和上面链接的答案之间的冲量计算有所不同。这是因为我避免使用角度计算,因为它们非常昂贵。相反,我正在计算一个法线,这样计算的计算效率更高。

最后一点,我的回答 here 解释了使用速率因子来平滑运动并为运动失真留出空间。

import SpriteKit

class GameScene: SKScene {
    var node: SKShapeNode! //The node.
    let path: [CGPoint] = [CGPoint(x: 100, y: 100),CGPoint(x: 100, y: 300),CGPoint(x: 300, y: 300),CGPoint(x: 300, y: 100)] //The path of points to travel.
    let repeats: Bool = true //Whether to repeat the path.
    var pathIndex = 0 //The index of the current point to travel.
    let pointRadius: CGFloat = 10 //How close the node must be to reach the destination point.
    let travelSpeed: CGFloat = 200 //Speed the node will travel at.
    let rate: CGFloat = 0.5 //Motion smoothing.

    override func didMoveToView(view: SKView) {
        node = SKShapeNode(circleOfRadius: 10)
        node.physicsBody = SKPhysicsBody(circleOfRadius: 10)
        node.physicsBody!.affectedByGravity = false
        self.addChild(node)
    }

    final func didReachPoint() {
        //We reached a point!
        pathIndex++

        if pathIndex >= path.count && repeats {
            pathIndex = 0
        }
    }

    override func update(currentTime: NSTimeInterval) {
        if pathIndex >= 0 && pathIndex < path.count {
            let destination = path[pathIndex]
            let displacement = CGVector(dx: destination.x-node.position.x, dy: destination.y-node.position.y)
            let radius = sqrt(displacement.dx*displacement.dx+displacement.dy*displacement.dy)
            let normal = CGVector(dx: displacement.dx/radius, dy: displacement.dy/radius)
            let impulse = CGVector(dx: normal.dx*travelSpeed, dy: normal.dy*travelSpeed)
            let relativeVelocity = CGVector(dx:impulse.dx-node.physicsBody!.velocity.dx, dy:impulse.dy-node.physicsBody!.velocity.dy);
            node.physicsBody!.velocity=CGVectorMake(node.physicsBody!.velocity.dx+relativeVelocity.dx*rate, node.physicsBody!.velocity.dy+relativeVelocity.dy*rate);
            if radius < pointRadius {
                didReachPoint()
            }
        }
    }
}

我做的很快,所以如果有错误,我深表歉意。我现在没有时间,但稍后我会添加一个显示解决方案的 gif。


关于碰撞的注意事项

为了修复碰撞过程中不稳定的运动,在两个物体碰撞后将 "rate" 属性 设置为 0 或最好设置一个非常低的数字以减少行进速度脉冲,这会给你更多运动失真的空间。然后在将来的某个时间点(可能是碰撞发生后的某个时间,或者 最好是 当 body 再次缓慢移动时)将速率设置回其初始值。如果您真的想要一个不错的效果,您实际上可以随着时间的推移将速率值从 0 逐渐增加到初始值,从而给自己一个平滑和渐进的加速。