找到弹跳SKSpriteNode将采用的路径

Joj*_*dmo 10 collision-detection ios sprite-kit swift

当一个SKSpriteNode在屏幕上反弹时,我想显示两条线 - 一个是节点前进的地方,第二个是节点从其他sprite节点反弹的方向.

基本上,我想做这样的事情:

在此输入图像描述

我可以使用球的速度相当容易地创建第一行,第二行也相当容易,但前提是我知道线将与另一个SKSpriteNode碰撞的位置.

对于第一行,我正在创建一个SKShapeNode,并将其路径设置为与球的速度相同的线,但我不确定如何找到球从最近的障碍物反弹后所采取的路径.

简而言之 - 如何找到移动物体与另一个物体碰撞的点,然后找到它与物体碰撞后移动的方向?如果我知道它会碰到什么物体,但这不是一个问题,但是不能做一些非常低效的暴力破坏,我不知道如何找到球将要碰撞的物体以及在哪里.

我正在寻找这样的东西:

let line: SKSpriteNode // this is the first line
let scene: SKScene // the scene that contains all of the objects

// this doesn't exist, but I'm looking for something that would do this
let collisionPoint: CGPoint = line.firstCollision(in: scene)
Run Code Online (Sandbox Code Playgroud)

就我的目的而言,精灵上没有环境因素(没有重力,空气阻力等),它以恒定速度移动,并且不会在篮板中失去任何速度,但如果答案可能包括一种计算任何环境中精灵的第n次反弹的方法.

Luc*_*tti 9

这是我们要打造的结果

在此输入图像描述

Ray Casting

这里使用的技术称为Ray Casting,你可以在我已经写过的类似答案中找到更多细节.

基本上我们使用SpriteKit物理引擎来确定绘制直线并获得该直线与物理实体之间的第一个交点.

当然你需要

  • 基于SpriteKit创建一个空的Xcode项目
  • 添加一个圆形精灵,我们称之为球
  • 在球上添加一个物理身体并给它一些速度

这是您需要的完整代码

扩展

将这2个扩展添加到项目中

extension CGVector {

    init(angle: CGFloat) {
        self.init(dx: cos(angle), dy: sin(angle))
    }

    func normalized() -> CGVector {
        let len = length()
        return len>0 ? self / len : CGVector.zero
    }

    func length() -> CGFloat {
        return sqrt(dx*dx + dy*dy)
    }

    static func / (vector: CGVector, scalar: CGFloat) -> CGVector {
        return CGVector(dx: vector.dx / scalar, dy: vector.dy / scalar)
    }

    func bounced(withNormal normal: CGVector) -> CGVector {
        let dotProduct = self.normalized() * normal.normalized()
        let dx = self.dx - 2 * (dotProduct) * normal.dx
        let dy = self.dy - 2 * (dotProduct) * normal.dy
        return CGVector(dx: dx, dy: dy)
    }

    init(from:CGPoint, to:CGPoint) {
        self = CGVector(dx: to.x - from.x, dy: to.y - from.y)
    }

    static func * (left: CGVector, right: CGVector) -> CGFloat {
        return (left.dx * right.dx) + (left.dy * right.dy)
    }

}

extension CGPoint {

    func length() -> CGFloat {
        return sqrt(x*x + y*y)
    }

    func distanceTo(_ point: CGPoint) -> CGFloat {
        return (self - point).length()
    }

    static func -(left: CGPoint, right: CGPoint) -> CGPoint {
        return CGPoint(x: left.x - right.x, y: left.y - right.y)
    }

}
Run Code Online (Sandbox Code Playgroud)

现场

现在Scene.swift用这个替换你所拥有的所有代码

class GameScene: SKScene {

    lazy var ball: SKSpriteNode = {
        return childNode(withName: "ball") as! SKSpriteNode
    }()

    override func didMove(to view: SKView) {
        self.physicsBody = SKPhysicsBody(edgeLoopFrom: frame)
    }

    override func update(_ currentTime: TimeInterval) {

        self.enumerateChildNodes(withName: "line") { (node, _) in
            node.removeFromParent()
        }

        guard let collision = rayCast(start: ball.position, direction: ball.physicsBody!.velocity.normalized()) else { return }

        let line = CGVector(from: ball.position, to: collision.destination)
        drawVector(point: ball.position, vector: line, color: .green)

        var nextVector = line.normalized().bounced(withNormal: collision.normal.normalized()).normalized()
        nextVector.dx *= 200
        nextVector.dy *= 200
        drawVector(point: collision.destination, vector: nextVector, color: .yellow)
    }

    private func rayCast(start: CGPoint, direction: CGVector) -> (destination:CGPoint, normal: CGVector)? {

        let endVector = CGVector(
            dx: start.x + direction.normalized().dx * 4000,
            dy: start.y + direction.normalized().dy * 4000
        )

        let endPoint = CGPoint(x: endVector.dx, y: endVector.dy)

        var closestPoint: CGPoint?
        var normal: CGVector?

        physicsWorld.enumerateBodies(alongRayStart: start, end: endPoint) {
            (physicsBody:SKPhysicsBody,
            point:CGPoint,
            normalVector:CGVector,
            stop:UnsafeMutablePointer<ObjCBool>) in

            guard start.distanceTo(point) > 1 else {
                return
            }

            guard let newClosestPoint = closestPoint else {
                closestPoint = point
                normal = normalVector
                return
            }

            guard start.distanceTo(point) < start.distanceTo(newClosestPoint) else {
                return
            }

            normal = normalVector
        }
        guard let p = closestPoint, let n = normal else { return nil }
        return (p, n)
    }

    private func drawVector(point: CGPoint, vector: CGVector, color: SKColor) {

        let start = point
        let destX = (start.x + vector.dx)
        let destY = (start.y + vector.dy)
        let to = CGPoint(x: destX, y: destY)

        let path = CGMutablePath()
        path.move(to: start)
        path.addLine(to: to)
        path.closeSubpath()
        let line = SKShapeNode(path: path)
        line.strokeColor = color
        line.lineWidth = 6
        line.name = "line"
        addChild(line)
    }
}
Run Code Online (Sandbox Code Playgroud)

它是如何工作的?

这种方法

func drawVector(point: CGPoint, vector: CGVector, color: SKColor)
Run Code Online (Sandbox Code Playgroud)

只需vectorstarting point给定的a中抽取a color.

这个

func rayCast(start: CGPoint, direction: CGVector) -> (destination:CGPoint, normal: CGVector)?
Run Code Online (Sandbox Code Playgroud)

使用上面讨论的Ray Casting技术来找到交点和相关的法向量.

最后在

func update(_ currentTime: TimeInterval)
Run Code Online (Sandbox Code Playgroud)

我们准备去

  • 删除上一帧中绘制的线条
  • 使用光线投射获取碰撞点
  • 划出球与碰撞点之间的界线
  • 计算球将反弹的方向
  • 使用上一步中获得的方向值绘制一条来自碰撞的直线

而已