【问题标题】:Swift 3 Generate evenly-spaced SKSpriteNodes along path drawn by userSwift 3 沿用户绘制的路径生成均匀分布的 SKSpriteNodes
【发布时间】:2017-06-23 21:55:20
【问题描述】:

大家!首先,我知道这个问题与Draw images evenly spaced along a path in iOS 非常相似。但是,这是在 Objective-C 中(我看不懂)并且它在使用 CGImageRefs 的普通 ViewController 中。我需要它迅速并使用 SKSpriteNodes(不是 CGImageRefs)。这是我的问题:

我正在尝试制作一个程序,让用户绘制一个简单的形状(如圆形)并沿着用户绘制的路径以固定间隔放置 SKSpriteNodes。我已经让它以缓慢的速度正常工作,但是如果用户绘制得太快,那么节点就会被放置得太远。下面是我慢慢画的例子:

用户绘制的路径,其中节点彼此相距大约 60 像素。蓝色是开始节点,紫色是结束节点。

目标是每个节点都有一个 PhysicsBody 来防止实体越过用户绘制的线(这些实体将无法挤入均匀间隔的节点之间)。但是,如果用户画得太快,就会出现我无法修复的防御缺口。例如:

请注意第 7 和第 8 个节点之间明显更大的间隙。这是因为我画得太快了。许多人的问题有些相似,但对我的任务没有帮助(例如,沿路径均匀分布特定数量的节点,而不是放置尽可能多的节点以使它们沿路径相距 60 像素)。

最后,这又是我的主要问题:如何将节点放置在沿用户绘制的任意形状路径完美间隔的位置?预先感谢您的帮助!这是我的 GameScene.swift 文件:

import SpriteKit

导入 GameplayKit

类 GameScene:SKScene {

let minDist: CGFloat = 60 //The minimum distance between one point and the next

var points: [CGPoint] = []
var circleNodes: [SKShapeNode] = []

override func didMove(to view: SKView) {


}

func getDistance (fromPoint: CGPoint, toPoint: CGPoint) -> CGFloat {

    let deltaX = fromPoint.x - toPoint.x
    let deltaY = fromPoint.y - toPoint.y

    let deltaXSquared = deltaX*deltaX
    let deltaYSquared = deltaY*deltaY

    return sqrt(deltaXSquared + deltaYSquared) //Return the distance

}


func touchDown(atPoint pos : CGPoint) {

    self.removeAllChildren()

    //The first time the user touches, we need to place a point and mark that as the firstCircleNode
    print(pos)
    points.append(pos)
    //allPoints.append(pos)

    let firstCircleNode = SKShapeNode(circleOfRadius: 5.0)

    firstCircleNode.fillColor = UIColor.blue

    firstCircleNode.strokeColor = UIColor.blue

    firstCircleNode.position = pos

    circleNodes.append(firstCircleNode)

    self.addChild(firstCircleNode)

}

func touchMoved(toPoint pos : CGPoint) {

    let lastIndex = points.count - 1 //The index of the last recorded point

    let distance = getDistance(fromPoint: points[lastIndex], toPoint: pos)
        //vector_distance(vector_double2(Double(points[lastIndex].x), Double(points[lastIndex].y)), vector_double2(Double(pos.x), Double(pos.y))) //The distance between the user's finger and the last placed circleNode

    if distance >= minDist {
        points.append(pos)

        //Add a box to that point
        let newCircleNode = SKShapeNode(circleOfRadius: 5.0)

        newCircleNode.fillColor = UIColor.red

        newCircleNode.strokeColor = UIColor.red

        newCircleNode.position = pos

        circleNodes.append(newCircleNode)

        self.addChild(newCircleNode)

    }

}

func touchUp(atPoint pos : CGPoint) {

    //When the user has finished drawing a circle:

    circleNodes[circleNodes.count-1].fillColor = UIColor.purple //Make the last node purple

    circleNodes[circleNodes.count-1].strokeColor = UIColor.purple

    //Calculate the distance between the first placed node and the last placed node:
    let distance = getDistance(fromPoint: points[0], toPoint: points[points.count-1])
        //vector_distance(vector_double2(Double(points[0].x), Double(points[0].y)), vector_double2(Double(points[points.count - 1].x), Double(points[points.count - 1].y)))

    if distance <= minDist { //If the distance is closer than the minimum distance

        print("Successful circle")

    } else { //If the distance is too far

        print("Failed circle")

    }

    points = []
    circleNodes = []

}


override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    for t in touches { self.touchDown(atPoint: t.location(in: self)) }
}

override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
    for t in touches { self.touchMoved(toPoint: t.location(in: self)) }
}

override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
    for t in touches { self.touchUp(atPoint: t.location(in: self)) }
}

override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
    for t in touches { self.touchUp(atPoint: t.location(in: self)) }
}


override func update(_ currentTime: TimeInterval) {
    // Called before each frame is rendered
}

}

【问题讨论】:

    标签: ios swift swift3 skspritenode cgpath


    【解决方案1】:

    您可以尝试调整矢量的大小:

    func touchMoved(toPoint pos : CGPoint) {
        let lastIndex = points.count - 1 //The index of the last recorded point
        let distance = getDistance(fromPoint: points[lastIndex], toPoint: pos)
        if distance >= minDist {
            // find a new "pos" which is EXACTLY minDist distant
            let vx = pos.x - points[lastIndex].x
            let vy = pos.y - points[lastIndex].y
            vx /= distance
            vy /= distance
            vx *= minDist
            vy *= minDist
            let newpos = CGPoint(x: vx, y:vy)
            points.append(newpos)
    
            //Add a box to that point
            let newCircleNode = SKShapeNode(circleOfRadius: 5.0)
            newCircleNode.fillColor = UIColor.red
            newCircleNode.strokeColor = UIColor.red
            newCircleNode.position = newpos // NOTE
            circleNodes.append(newCircleNode)
            self.addChild(newCircleNode)
        }
    }
    

    它可能并不完美,但可能看起来更好。

    【讨论】:

    • 这是一个很好的建议,但是,当我拖动鼠标/手指时,此代码只会导致在中心绘制一圈节点。不过,你确实给了我一个想法。也许如果我在一个 CGPoints 数组中跟踪用户绘制的路径,然后使用它们来跟踪一系列大小为 60 的向量,每个向量都指向下一个点,该点距离上一个点至少 60 像素。可能就是这样。我会做的,如果可行,稍后再发布!
    • 如果你拖动一条直线会发生什么?
    • 在我将很快分享的新实现中,它只是产生一条均匀放置的节点的直线。
    【解决方案2】:

    我想通了!我受到 Christian Cerri 的建议的启发,所以我使用以下代码来制作我想要的东西:

    import SpriteKit
    import GameplayKit
    
    // MARK: - GameScene
    
    class GameScene: SKScene {
    
    // MARK: - Allows me to work with vectors. Derived from https://www.raywenderlich.com/145318/spritekit-swift-3-tutorial-beginners
    
        func subtract(point: CGPoint, fromPoint: CGPoint) -> CGPoint {
    
            return CGPoint(x: point.x - fromPoint.x, y: point.y - fromPoint.y) //Returns a the first vector minus the second
    
        }
    
        func add(point: CGPoint, toPoint: CGPoint) -> CGPoint {
    
            return CGPoint(x: point.x + toPoint.x, y: point.y + toPoint.y) //Returns a the first vector minus the second
    
        }
    
        func multiply(point: CGPoint, by scalar: CGFloat) -> CGPoint {
    
            return CGPoint(x: point.x * scalar, y: point.y * scalar)
    
        }
    
        func divide(point: CGPoint, by scalar: CGFloat) -> CGPoint {
    
            return CGPoint(x: point.x / scalar, y: point.y / scalar)
    
        }
    
        func magnitude(point: CGPoint) -> CGFloat {
    
            return sqrt(point.x*point.x + point.y*point.y)
    
        }
    
        func normalize(aPoint: CGPoint) -> CGPoint {
    
            return divide(point: aPoint, by: magnitude(point: aPoint))
    
        }
    
        // MARK: - Properties
    
        let minDist: CGFloat = 60
    
        var userPath: [CGPoint] = [] //Holds the coordinates collected when the user drags their finger accross the screen
    
        override func didMove(to view: SKView) {
    
    
    
        }
    
        // MARK: - Helper methods
    
        func getDistance (fromPoint: CGPoint, toPoint: CGPoint) -> CGFloat
        {
    
            let deltaX = fromPoint.x - toPoint.x
            let deltaY = fromPoint.y - toPoint.y
    
            let deltaXSquared = deltaX*deltaX
            let deltaYSquared = deltaY*deltaY
    
            return sqrt(deltaXSquared + deltaYSquared) //Return the distance
    
        }
    
        func touchDown(atPoint pos : CGPoint) {
    
            userPath = []
            self.removeAllChildren()
    
            //Get the first point the user makes
            userPath.append(pos)
    
        }
    
        func touchMoved(toPoint pos : CGPoint) {
    
            //Get every point the user makes as they drag their finger across the screen
            userPath.append(pos)
    
        }
    
        func touchUp(atPoint pos : CGPoint) {
    
            //Get the last position the user was left touching when they've completed the motion
            userPath.append(pos)
    
            //Print the entire path:
            print(userPath)
            print(userPath.count)
    
            plotNodesAlongPath()
    
        }
    
        /**
         Puts nodes equidistance from each other along the path that the user placed
         */
        func plotNodesAlongPath() {
    
            //Start at the first point
            var currentPoint = userPath[0]
    
            var circleNodePoints = [currentPoint] //Holds the points that I will then use to generate circle nodes
    
            for i in 1..<userPath.count {
    
                let distance = getDistance(fromPoint: currentPoint, toPoint: userPath[i]) //The distance between the point and the next
    
                if distance >= minDist { //If userPath[i] is at least minDist pixels away
    
                    //Then we can make a vector that points from currentPoint to userPath[i]
                    var newNodePoint = subtract(point: userPath[i], fromPoint: currentPoint)
    
                    newNodePoint = normalize(aPoint: newNodePoint) //Normalize the vector so that we have only the direction and a magnitude of 1
    
                    newNodePoint = multiply(point: newNodePoint, by: minDist) //Stretch the vector to a length of minDist so that we now have a point for the next node to be drawn on
    
                    newNodePoint = add(point: currentPoint, toPoint: newNodePoint) //Now add the vector to the currentPoint so that we get a point in the correct position
    
                    currentPoint = newNodePoint //Update the current point. Next we want to draw a point minDist away from the new current point
    
                    circleNodePoints.append(newNodePoint) //Add the new node
    
                }
                //If distance was less than minDist, then we want to move on to the next point in line
    
            }
    
            generateNodesFromPoints(positions: circleNodePoints)
    
        }
    
        func generateNodesFromPoints(positions: [CGPoint]) {
            print("generateNodesFromPoints")
            for pos in positions {
    
                let firstCircleNode = SKShapeNode(circleOfRadius: 5.0)
    
                firstCircleNode.fillColor = UIColor.blue
    
                firstCircleNode.strokeColor = UIColor.blue
    
                firstCircleNode.position = pos //Put the node in the correct position
    
                self.addChild(firstCircleNode)
    
            }
    
        }
    
        // MARK: - Touch responders
    
        override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
            for t in touches { self.touchDown(atPoint: t.location(in: self)) }
        }
    
        override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
            for t in touches { self.touchMoved(toPoint: t.location(in: self)) }
        }
    
        override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
            for t in touches { self.touchUp(atPoint: t.location(in: self)) }
        }
    
        override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
            for t in touches { self.touchUp(atPoint: t.location(in: self)) }
        }
    
    
        override func update(_ currentTime: TimeInterval) {
            // Called before each frame is rendered
        }
    }
    

    结果如下:

    无论用户移动手指的速度有多快,它都会沿其路径均匀地放置节点。非常感谢您的帮助,希望以后能帮助到更多的人!

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-01-06
      • 2015-06-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-05-08
      • 1970-01-01
      相关资源
      最近更新 更多