I'm new to SpriteKit and I'm trying to simulate a population of 1000 moving individuals each being represented by a simple circle. I have an awful frame rate (around 6/7 fps) so I reduced to 100 and reached 35 fps...
My nodes are the simplest in the world, I'm using the entity component system from GameplayKit, but nothing fancy.
So what can I do to improve and reach 60 fps?
First my shape entity, later in the process the color and filling will change for some individuals, but here there's only a simple circle node:
class ShapeComponent : GKComponent {
let node = SKShapeNode(ellipseOf: CGSize(width: 10, height: 10))
override func didAddToEntity() {
node.entity = entity
}
override func willRemoveFromEntity() {
node.entity = nil
}
}
Then my movement component, having just a speed and an angle. I have even stored sine and cosine to limit computation:
class MovementComponent : GKComponent {
var speed : CGFloat = 25
var direction : CGFloat = .random(in: 0...(2 * .pi)) {
didSet { updateSinCos() }
}
var sinDir : CGFloat = 0
var cosDir : CGFloat = 0
override init() {
super.init()
updateSinCos()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
var shapeComponent: ShapeComponent {
guard let shapeComponent = entity?.component(ofType: ShapeComponent.self)
else { fatalError("A MovementComponent's entity must have a ShapeComponent") }
return shapeComponent
}
func updateSinCos() {
sinDir = sin(direction)
cosDir = cos(direction)
}
override func update(deltaTime: TimeInterval) {
super.update(deltaTime: deltaTime)
// Declare local versions of computed properties so we don't compute them multiple times.
let delta = speed * CGFloat(deltaTime)
let vector = CGVector(dx: delta * sinDir, dy: delta * cosDir)
let node = shapeComponent.node
let currentPosition = node.position
node.position = CGPoint(x: currentPosition.x + vector.dx, y: currentPosition.y + vector.dy)
}
}
And at last my entity, with movement and shape components:
class IndividualEntity : GKEntity {
var shapeComponent: ShapeComponent {
guard let shapeComponent = component(ofType: ShapeComponent.self)
else { fatalError("A IndividualEntity must have a ShapeComponent") }
return shapeComponent
}
var movementComponent: MovementComponent {
guard let movementComponent = component(ofType: MovementComponent.self)
else { fatalError("A IndividualEntity must have a MovementComponent") }
return movementComponent
}
override init() {
super.init()
addComponent(ShapeComponent())
addComponent(MovementComponent())
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
In my scene:
func setUpScene() {
for _ in 1...100 {
let position = CGPoint(x: .random(in: 0..<size.width) - 0.5*size.width, y: .random(in: 0..<size.height) - 0.5*size.height)
addIndividual(at: position)
}
}
var componentSystem = GKComponentSystem(componentClass: MovementComponent.self)
var individuals = [IndividualEntity]()
func addIndividual(at pos: CGPoint) {
let individual = IndividualEntity()
individual.shapeComponent.node.position = pos
addChild(individual.shapeComponent.node)
componentSystem.addComponent(foundIn: individual)
individuals.append(individual)
}
var lastUpdateTimeInterval: TimeInterval = 0
let maximumUpdateDeltaTime: TimeInterval = 1.0 / 60.0
override func update(_ currentTime: TimeInterval) {
guard view != nil else { return }
var deltaTime = currentTime - lastUpdateTimeInterval
deltaTime = deltaTime > maximumUpdateDeltaTime ? maximumUpdateDeltaTime : deltaTime
lastUpdateTimeInterval = currentTime
componentSystem.update(deltaTime: deltaTime)
}
So if someone can help me with this problem... I just want those circles to wander, and later respond to collision by changing direction and color.
Thanks in advance.