Swift 3 沿着用户绘制的路径生成均匀分布的SKSpriteNode
Swift 3 Generate evenly-spaced SKSpriteNodes along path drawn by user
大家好!首先,我知道这个问题与 Draw images evenly spaced along a path in iOS 非常相似。但是,那是在 Objective-C(我无法阅读) 和 中,它在正常的 ViewController 中与 CGImageRefs 一起工作。我在 swift 中需要它并使用 SKSpriteNodes(不是 CGImageRefs)。这是我的问题:
我正在尝试制作一个程序,让用户绘制一个简单的形状(如圆形)并沿着用户绘制的路径以固定间隔放置 SKSpriteNode。我已经让它以缓慢的速度正常工作,但如果用户绘制得太快,那么节点就会被放置得太远。这是我慢慢画的例子:
用户绘制的路径,节点之间的距离约为 60 像素。蓝色为起始节点,紫色为结束节点。
目标是每个节点都有一个 physicsBody,防止实体越过用户绘制的线(这些实体将无法挤在均匀间隔的节点之间)。但是,如果用户画得太快,就会出现我无法修复的防御缺口。例如:
请注意第 7 个和第 8 个节点之间明显更大的间隙。发生这种情况是因为我画得太快了。许多人的问题略有相似但对我的任务没有帮助(例如,将特定数量的节点沿路径均匀分布,而不是放置尽可能多的节点以使它们沿路径相距 60 像素)。
总而言之,这又是我的主要问题:如何沿着用户绘制的任何形状的路径完美地放置节点?预先感谢您的帮助!这是我的 GameScene.swift 文件:
import SpriteKit
导入 GameplayKit
class 游戏场景: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
}
}
您可以尝试调整矢量大小:
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)
}
}
它可能不完美,但看起来会更好。
我想通了!我受到 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
}
}
结果如下:
无论用户移动手指的速度有多快,它都会沿着路径均匀放置节点。非常感谢您的帮助,希望以后能帮助到更多的人!
大家好!首先,我知道这个问题与 Draw images evenly spaced along a path in iOS 非常相似。但是,那是在 Objective-C(我无法阅读) 和 中,它在正常的 ViewController 中与 CGImageRefs 一起工作。我在 swift 中需要它并使用 SKSpriteNodes(不是 CGImageRefs)。这是我的问题:
我正在尝试制作一个程序,让用户绘制一个简单的形状(如圆形)并沿着用户绘制的路径以固定间隔放置 SKSpriteNode。我已经让它以缓慢的速度正常工作,但如果用户绘制得太快,那么节点就会被放置得太远。这是我慢慢画的例子:
用户绘制的路径,节点之间的距离约为 60 像素。蓝色为起始节点,紫色为结束节点。
目标是每个节点都有一个 physicsBody,防止实体越过用户绘制的线(这些实体将无法挤在均匀间隔的节点之间)。但是,如果用户画得太快,就会出现我无法修复的防御缺口。例如:
请注意第 7 个和第 8 个节点之间明显更大的间隙。发生这种情况是因为我画得太快了。许多人的问题略有相似但对我的任务没有帮助(例如,将特定数量的节点沿路径均匀分布,而不是放置尽可能多的节点以使它们沿路径相距 60 像素)。
总而言之,这又是我的主要问题:如何沿着用户绘制的任何形状的路径完美地放置节点?预先感谢您的帮助!这是我的 GameScene.swift 文件:
import SpriteKit
导入 GameplayKit
class 游戏场景: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
}
}
您可以尝试调整矢量大小:
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)
}
}
它可能不完美,但看起来会更好。
我想通了!我受到 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
}
}
结果如下:
无论用户移动手指的速度有多快,它都会沿着路径均匀放置节点。非常感谢您的帮助,希望以后能帮助到更多的人!