ForceDirectedScene
这是针对节点集合解决的 n-body 问题的一个解决方案。该库计算在 SpriteKit 场景中生成力图所需的力。仅在节点上的电荷之间模拟排斥/吸引作用力。《code>
安装
ForceDirectedScene 通过 CocoaPods 提供。要安装它,只需在 Podfile 中添加以下行即可
pod 'ForceDirectedScene'
使用方法
步骤 1:实现 ForceBody 协议
在您的数据模型上实现 ForceBody
协议。我们提供这个协议,因此您不必提供具有附加 SKPhysicsBody
的 SKNode
列表
protocol ForceBody {
var position: CGPoint { get }
var charge: CGFloat { get }
func applyForce(force: CGVector)
}
示例:假设您的节点是 MyNode
类型的实例,该类在一个属性 skNode
中存储一个 SKNode
以在 SpriteKit 场景中渲染。然后您可以将协议实现为
extension MyNode: ForceBody {
public var position: CGPoint {
get {
return self.skNode.position
}
}
public var charge: CGFloat {
get {
if let physics = self.skNode.physicsBody {
return physics.charge
}
return 0.0
}
}
public func applyForce(force: CGVector) {
self.skNode.physicsBody?.applyForce(force)
}
}
步骤 2:在每个节点上设置物理体
您可以使用 SpriteKit 提供的所有工具来设置您的场景。如果通过协议将 physicsBody 的 charge 属性共享给 ForceDirectedGraph,则您的节点可以同时受到电场和相互排斥或吸引力的作用。此外,barnes-hut 算法使用四叉树来加速模拟,这需要显式定义强制导向图的边界,因此我们建议设置约束以将您的节点的运动限制到您定义的任何边界。
我们还建议设置一个强有力的 linearDamping
属性。
for node in mynodes {
node.skNode = SKShapeNode( ... )
node.skNode.positition = CGPoint( ... )
node.skNode.physicsBody = SKPhysicsBody(circleOfRadius: 10.0)
node.skNode.physicsBody?.isDynamic = true
node.physicsBody?.charge = 5.5
node.physicsBody?.linearDamping = 1.3
node.skNode.constraints = [
SKConstraint.positionX(SKRange(lowerLimit: 0, upperLimit: self.view.bounds.width)),
SKConstraint.positionY(SKRange(lowerLimit: 0, upperLimit: self.view.bounds.height))
]
scene.addChild(node.skNode)
}
步骤 3:为链接设置弹簧关节
您可以使用弹簧关节构建强制导向图中的链接。例如
for link in links {
let src = link.sourceSKNode
let dest = link.destinationSKNode
let spring = SKPhysicsJointSpring.joint(withBodyA: src.physicsBody!, bodyB: dest.physicsBody!, anchorA: src.position, anchorB: dest.position)
spring.damping = 10.0
spring.frequency = 0.25
scene.physicsWorld.add(spring)
}
(请注意,您可能还希望为每个链接创建一个 SKShapeNode
以在 src 和 dest 之间渲染线条)
步骤 4:在您的 SKSceneDelegate 中创建 ForceDirectedGraph
ForceDirectedGraph 构造函数有两个必需的参数:(1) 图形显示区域的边界,以及 (2) 符合 ForceBody 协议的对象数组
fdGraph = ForceDirectedGraph(bounds: self.view.bounds, nodes: mynodes)
然后,在 SKSceneDelegate 的 update 方法中调用图形的更新方法
func update(_ currentTime: TimeInterval, for scene: SKScene) {
fdGraph.update()
}
您还可能想要更新链接的 SKShapeNode
的位置。我们建议在 didApplyConstraints
方法中这样做
func didApplyConstraints(for scene: SKScene) {
var path: UIBezierPath
for link in links {
let src = link.sourceSKNode
let dest = link.destinationSKNode
let lineNode = link.skNode
path = UIBezierPath()
path.move(to: src.position)
path.addLine(to: dest.position)
lineNode.path = path.cgPath
}
}
API
ForceBody
属性 | 描述 |
---|---|
位置 | 一个必须返回节点当前场景位置的 getter |
电荷 | 获取器,必须返回当前节点的电荷。这不必与SKPhysicsBody相同。可以是正的或负的。同种电荷相互排斥。异种电荷相互吸引。 |
applyForce | 一个函数,最终必须将供给的力传递给每个节点的applyForce 方法 |
力导向图
公共属性 | 描述 |
---|---|
theta | Barnes-Hut算法的阈值。低值提高了模拟精度,但计算成本更高。高值可以加快模拟。 |
maxDistance | 两个节点能互相影响的最大的距离。比这个距离更远的节点将不再影响彼此施加的力。 |
minDistance | 两个节点之间各自影响对方所需的最小距离。比这个距离更近的节点将停止影响彼此。 |
center? | 所有节点应试图聚集的点。当不为nil时,会导致向每个节点应用额外的常数力,将节点指向定义的中心。如果中心的任何一个x 或y 坐标设置为CGFloat.isFinite 为false,则不会在该方向上应用力,例如,中心为CGPoint(x: 50.0, y: CGFloat.infinity) 将导致节点围绕x=50.0的垂直线聚集。 |
centeringStrength | 定义将施加到每个节点的力在将其移动到集群点/线上的强度 |
bounds | 一个CGRect,定义力导向图场景的最大边界。如果场景将节点推过这些边界,将导致未定义行为。 |
update() | 必须定期调用的方法来更新模拟,通常在SKSceneDelegate方法中。 |
init() | 接受每个这些属性的初始化器。请参阅以下内容。 |
这些可以传递给构造函数。签名中定义的默认值
public init(bounds: CGRect,
nodes: Array<ForceBody>,
theta: CGFloat = 0.5,
min: CGFloat = 0.0,
max: CGFloat = CGFloat.infinity,
center: CGPoint? = nil,
centeringStrength: CGFloat = 0.0002)
作者
Dylan Knight, [email protected]
许可证
ForceDirectedScene可在MIT许可证下使用。有关更多信息,请参阅LICENSE文件。