代码之家  ›  专栏  ›  技术社区  ›  Chris

带枚举体的反弹光线raystart

  •  3
  • Chris  · 技术社区  · 6 年前

    我想追踪子弹在我的精灵游戏场景中移动的路径。

    我不知道如何计算反射角,给定接触点和接触法线。

    我想计算路径,超过5次反射/反弹,所以首先我:

    1. 然后我用那个接触点作为我下一次反射/反弹的开始…但是我正在努力确定终点应该设置为。。。。

    认为 我应该做的是得到接触点和接触法线之间的角度,然后计算一个与之相反的新点。。。

        var points: [CGPoint] = []
        var start: CGPoint = renderComponent.node.position
        var end: CGPoint = crossHairComponent.node.position
    
        points.append(start)
    
        var closestNormal: CGVector = .zero
    
        for i in 0...5 {
    
            closestNormal = .zero
            var closestLength: CGFloat? = nil
            var closestContact: CGPoint!
    
            // Get the closest contact point.
            self.physicsWorld.enumerateBodies(alongRayStart: start, end: end) { (physicsBody, contactPoint, contactNormal, stop) in
    
                let len = start.distance(point: contactPoint)
    
                if closestContact == nil {
                    closestNormal = contactNormal
                    closestLength = len
                    closestContact = contactPoint
                } else {
                    if len <= closestLength! {
                        closestLength = len
                        closestNormal = contactNormal
                        closestContact = contactPoint
                    }
                }
            }
    
    
            // This is where the code is just plain wrong and my math fails me.
            if closestContact != nil {
    
                // Calculate intersection angle...doesn't seem right?
                let v1: CGVector = (end - start).normalized().toCGVector()
                let v2: CGVector = closestNormal.normalized()
                var angle = acos(v1.dot(v2)) * (180 / .pi)
    
                let v1perp = CGVector(dx: -v1.dy, dy: v1.dx)
                if(v2.dot(v1perp) > 0) {
                    angle = 360.0 - angle
                }
    
                angle = angle.degreesToRadians
    
    
                // Set the new start point
                start = closestContact
    
                // Calculate a new end point somewhere in the distance to cast a ray to, so we can repeat the process again
                let x = closestContact.x + cos(angle)*100
                let y = closestContact.y + sin(-angle)*100
                end = CGPoint(x: x, y: y)  
    
                // Add points to array to draw them on the screen
                points.append(closestContact)
                points.append(end)
            }
    
        }
    
    1 回复  |  直到 6 年前
        1
  •  2
  •   Luca Angeletti    6 年前

    我想你是在找这样的东西吧?

    enter image description here

    一。工作代码

    1. scene.scaleMode = .resizeFill

    2. 删除在中找到的常用标签 GameScene.sks

    3. 替换 Scene.swift

    >

    import SpriteKit
    
    class GameScene: SKScene {
    
        override func didMove(to view: SKView) {
            self.physicsBody = SKPhysicsBody(edgeLoopFrom: frame)
        }
    
        var angle: CGFloat = 0
    
        override func update(_ currentTime: TimeInterval) {
            removeAllChildren()
            drawRayCasting(angle: angle)
            angle += 0.001
        }
    
        private func drawRayCasting(angle: CGFloat) {
            let colors: [UIColor] = [.red, .green, .blue, .orange, .white]
    
            var start: CGPoint = .zero
            var direction: CGVector = CGVector(angle: angle)
    
            for i in 0...4 {
                guard let result = rayCast(start: start, direction: direction) else { return }
                let vector = CGVector(from: start, to: result.destination)
    
                // draw
                drawVector(point: start, vector: vector, color: colors[i])
    
                // prepare for next iteration
                start = result.destination
                direction = vector.normalized().bounced(withNormal: result.normal.normalized()).normalized()
            }
        }
    
        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
            addChild(line)
        }
    }
    
    
    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)
        }
    
    }
    

    让我们看看这段代码的作用。我们从底部开始。

    三。 CGPoint CGVector

    这些只是简单的扩展(主要来自 Ray Wenderlich's repository on GitHub )为了简化我们要进行的几何运算。

    四。 drawVector(point:vector:color)

    vector color 从给定的开始 point .

    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
        addChild(line)
    }
    

    5个。 rayCast(start:direction) -> (destination:CGPoint, normal: CGVector)?

    此方法执行光线投射并返回光线与物理体碰撞时进入的最接近点。

    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)
    }
    

    几乎是壁橱 ?

    这意味着目的地必须至少距离起点1点

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

    因为没有这条规则,射线会被困在一个物理体中,永远无法脱离它。

    drawRayCasting(angle)

    这种方法基本上保持了局部变量的最新状态,以正确地生成5个段。

    private func drawRayCasting(angle: CGFloat) {
        let colors: [UIColor] = [.red, .green, .blue, .orange, .white]
    
        var start: CGPoint = .zero
        var direction: CGVector = CGVector(angle: angle)
    
        for i in 0...4 {
            guard let result = rayCast(start: start, direction: direction) else { return }
            let vector = CGVector(from: start, to: result.destination)
    
            // draw
            drawVector(point: start, vector: vector, color: colors[i])
    
            // prepare next direction
            start = result.destination
            direction = vector.normalized().bounced(withNormal: result.normal.normalized()).normalized()
        }
    }
    

    第一段的起点等于零,一个方向将角度参数下移。

    段2到5使用上一段的终点和“镜像方向”。

    更新(当前时间:时间间隔)

    在这里,我只是调用drawRayCasting每帧通过当前角度值和增加0.001的角度。

    var angle: CGFloat = 0
    override func update(_ currentTime: TimeInterval) {
        removeAllChildren()
        drawRayCasting(angle: angle)
        angle += 0.001
    }
    

    6。 didMove(to view: SKView)

    最后,我在场景周围创建了一个物理体,以使光线在边界上反弹。

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

    7。总结

    如果你有任何疑问,请告诉我。

    更新

    反弹函数中有一个错误。这妨碍了对反射光线的正确计算。

    enter image description here