如何设置圆形的物理属性,使其沿着给定路径移动。

8

物理圆体的运动对于我想要实现的目标来说太过不规则。我希望限制它,使其沿着特定路径触碰到特定点(或一系列点),如下图所示。我该如何设置物理属性以遍历类似的路径?

enter image description here


1
只需设置速度,当它到达新的点时,给它一个针对下一个点的新速度。 - Kendel
2
SKAction followPath. - sangony
2个回答

6
如何设置圆形的物理属性,使其沿着给定的路径移动?
本质上,您正在寻求使用实时运动将节点移动到特定点。我在这里给出了一个答案,展示了如何做到这一点,但考虑到这个问题收到的赞数,我将提供更详细的答案。
我链接的答案没有提供遍历点路径的方法。因此,在下面,我提供了一种解决方案,展示了如何完成这项任务。它只是简单地移动到路径中的每个点,每次节点到达一个点时,我们增加索引以移动到下一个点。我还添加了一些变量,用于旅行速度、速率(使运动更平滑或静态)以及节点是否应重复路径。您可以进一步扩展此解决方案,以更好地满足游戏的需求。我肯定会考虑子类化一个节点,并将此行为构建到其中,以便您可以重复使用多个节点的此运动。
最后,请注意,您可能会注意到我以下解决方案中的冲量计算与我上面链接的答案不同。这是因为我避免使用角度计算,因为它们非常昂贵。相反,我正在计算法线,以使计算更具计算效率。
最后,请注意,我的这里的答案解释了使用速率因子来平滑运动并留出运动扭曲的空间。
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或者更好地是一个非常小的数字,以减少行驶速度的冲量,这样可以给你更多的变形空间。然后在未来的某个时间点(可能是碰撞发生后一段时间,或者最好是当物体再次缓慢移动时)将速率恢复到初始值。如果您真的想要一个好的效果,您实际上可以随着时间的推移逐渐将速率值从0增加到初始值,从而使自己获得平滑和渐进的加速度。


让我进一步解释。基本上需要两个点来确定方向。我使用path[pathIndex-2] = CGPoint(x: 140, y: 90)在接触/碰撞后更改了一个点。这有时会给我想要的结果。其他时候,当运行相同的代码而没有任何更改时,path[pathIndex-2]是不同的点。日志显示pathIndex在3和4之间变化。这是我目前的问题。我不知道更新方式是否会导致不一致的值。 - NSSwift
@NSSwift 不应该使用 path[pathIndex-2],因为 pathIndex 取决于节点移动到哪个点。例如,当节点移动到第一个点时,pathIndex = 0,当它到达那里时,我们将 pathIndex 增加 1,这会强制节点前往第二个点,以此类推。你应该明确地更改点。例如,如果你想更改第二个点,则应该使用 path[1]。或者如果你想更改第一个点,则应该使用 path[0]。目前你所做的是设置节点移动到两个位置之前的点,这就是为什么你会注意到差异的原因。 - Epic Byte
3
@EpicByte 不错的解决方案。我发现如果圆形物体与另一个物体接触/碰撞,圆形物体会不规则地移动,迫使它在路径中走向预定点。当圆形物体与另一个物体接触/碰撞时,我正在尝试将圆形物体移动到 ballPath 中的下一个点。这里的想法是计算/使用物体的接触点作为某个临时点,以便让它到达 ballPath 的下一个点。我不太确定该如何处理。 - NSologistic
@NSologistic 为了修复运动不稳定的问题,在两个物体碰撞后,将“速率”属性设置为0或者一个非常低的数字,以减少行进速度冲量,从而给您更多的运动扭曲空间。然后在未来的某个时间点(也许是碰撞发生后的一段时间,或者最好是当物体再次缓慢移动时),将速率恢复到初始值。如果您真的想要一个很好的效果,实际上可以随着时间的推移逐渐将速率值从0加速到初始值,以获得平滑和逐渐加速的效果。 - Epic Byte
@EpicByte 谢谢,但是我的问题后面怎么办呢?例如,如果一个物体在移动到 ballPath[x] 时与圆形物体发生碰撞,我希望将该物体移动到 ballPath[x+1],而不是让它到达 ballPath[x]。有点像圆形物体与其碰撞的物体的接触点暂时取代了 ballPath[x] 的位置。 - NSologistic
显示剩余16条评论

1

使用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) 以创建场景的大小。
预览:

enter image description here


1
从这篇文章中,我认为@NSSwift想要使用物理属性来实现这个。 - NSologistic

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接