In most games, objects are being added and removed from the game world as the game progresses. This is an operation that affects memory usage and also triggers possible garbage collection.
I was looking for a solution to be able to remove objects in batches instead of having custom code that handles removal of objects scattered throughout my games.
I came up with a solution based on using a component together with a component system in GameplayKit.
By removing objects in batches I get control of exactly then during the game's update loop removal of objects are happening and also get the code better structured and performant to do these operations that affects memory in one go instead of being fragmented during the frame cycle.
It has also allowed me to get cleaner and more consolidated code with a single location that handles the removal of objects.
The Remove Component
The remove component is super simple; all it does is add itself to a component system, so it can be processed later.
import GameplayKit
/// Flags an entity to be removed from the game.
class RemoveComponent: Component {
// MARK: Component Life Cycle
override func didAddToEntity() {
super.didAddToEntity()
// Register with the removeComponentSystem.
scene?.removeComponentSystem.addComponent(self)
}
}
We use didAddToEntity()
to run our code automatically as soon as we add the component to an entity.
The component system the remove component adds itself to is defined in the scene class where the game runs. As almost all my entities and their added components needs to reference the scene file, I'm letting all my entities have a reference to the scene, and then I don't use GKComponent
directly but an extended version with a helper property so I always can reference the scene from components.
It looks something like this.
class Component: GKComponent {
// MARK: Convenience Properties
/// The scene the owning entity is added to.
var scene: GameScene? { (entity as? Entity)?.scene }
}
The RemoveComponent
is a child of my Component
and not GKComponent
so I can use the scene
property in didAddToEntity()
method.
The Component System
In my game scenes where I define my different component systems, I've setup a dedicated GKComponentSystem
which only has the purpose of handling removals of entities from the game.
class GameScene: SKScene {
// MARK: Component Systems
/// Component System to remove entities from the game.
let removeComponentSystem = GKComponentSystem(componentClass: RemoveComponent.self)
}
In the life cycle of SKScene
1 didFinishUpdate()
is the last method called just before the wait for the next frame begins. That method should be a great choice for us to use for the removal of our objects from the game.
At this time everything in the game is done executing during this update loop, so we end this cycle by batch removing all objects flagged for removal.
override func didFinishUpdate() {
super.didFinishUpdate()
// Remove entities flagged for removal.
for component in removeComponentSystem.components {
if let entity = component.entity as? Entity {
// Remove the entity and it's resources.
remove(entity: entity)
}
}
}
When iteration through the remove component system to get a reference for each entity that has the RemoveCompenent
added, we call a remove method that has the actual logic to remove each entity from the game world.
A remove()
method would most likely be unique for each game project. For me, I usually end up with something like this.
/// Removes an entity from the game world.
func remove(entity: Entity) {
// Remove sprite component as that removes the nodes from the scene.
entity.removeComponent(ofType: SpriteComponent.self)
// Remove the reference from the entities set.
entities.remove(entity)
}
I use a GKComponent
that handles the sprite and it knows how to visually remove itself from the game world in the willRemoveFromEntity()
method that is automatically called when a component is removed.
I keep a set with references to all entities currently in the game world, which I remove the entity from.
Using the Remove Component
That's basically it; the only thing that remains is to actually start using the RemoveComponent
when we need to flag an object for removal.
All we have to do to remove an object from the game is to add the RemoveComponent
to the entity.
Let's say that we have a bullet in the scene and we want to remove it when it hits its target. Then we simply can add the RemoveComponent
in the contactDidBegin
method.
func contactDidBegin(with other: GKEntity, contact: SKPhysicsContact) {
// Flag the bullet for removal.
addComponent(RemoveComponent())
}
Or we might have a particle emitter with a lifetime of 1.5 seconds that we want to remove when done playing. Then we could add the RemoveComponent
by using an SKAction
.
// remove emitter when done
let wait = SKAction.wait(forDuration: 1.5)
let remove = SKAction.run { [weak self] in
self?.addComponent(RemoveComponent())
}
run(SKAction.sequence([wait, remove]))
We use the run SKAction
and a sequence to add the RemoveComponent
after the specified time has elapsed.
Where to Next
With this structure in place where we have a separate system that tracks objects for removal, it would be interesting to keep building on this concept to separate some of this logic away from the game loop.
For instance, we could potentially see if it was possible to use a background thread to iterate through the component system to get more time left on the main thread used by the game for a performance increase.
But that's a project for another day.
-
SKScene Life Cycle. Responding to Frame-Cycle Events. ↩
Top comments (0)