Skip to content
Simon edited this page Oct 16, 2023 · 8 revisions

To create and add entities to a world use the entity function of the world. Let's create an entity with a PositionComponent and SpriteComponent:

data class Position(
    var x: Float,
    var y: Float,
) : Component<Position> {
    override fun type() = Position

    companion object : ComponentType<Position>()
}

data class Sprite(
    var texturePath:String
) : Component<Sprite> {
    override fun type() = Sprite

    companion object : ComponentType<Sprite>()
}

fun main() {
    val world = configureWorld {}

    val entity: Entity = world.entity {
        it += Position(5f, 5f)
        it += Sprite("texture.png")
    }
}

There might be situations where you need to execute a specific code when a component gets added or removed from an entity. This can be done via component lifecycle methods in Fleks. They are special methods (onAdd and onRemove) on a component that gets executed within the context of the World of the entity when that component gets added or removed.

Here is an example of a component, Box2dComponent, that reacts to itself being added or removed to an entity. It also relies on injection, which requires a World context. When added to an entity it creates a body and on removal it destroys it:

import com.badlogic.gdx.physics.box2d.World as PhysicWorld

class Box2dComponent : Component<Box2dComponent> {
    lateinit var body: Body

    override fun type() = Box2dComponent

    override fun World.onAdd(entity: Entity) {
        body = inject<PhysicWorld>().createBody( /* body creation code omitted */)
        body.userData = entity
    }

    override fun World.onRemove(entity: Entity) {
        body.world.destroyBody(body)
        body.userData = null
    }
    
    companion object : ComponentType<Box2dComponent>()
}

fun main() {
    val b2dWorld = PhysicWorld()

    val world = configureWorld {
        injectables {
            add(b2dWorld)
        }
    }

    val entity = world.entity {
        // this triggers the onAdd hook
        it += Box2dComponent()
    }

    // to update an entity outside a system, family or hook, use Kotlin's "with"
    // to run code in context of a World. This enables certain entity extension functions.
    with(world) {
        // Use 'configure' to update the components of an entity.
        entity.configure {
            // this triggers the onRemove hook
            entity -= Box2dComponent
        }
    }
}

Since Fleks 2.5 an entity is no longer a value class of an Int. It is now a data class containing the id of the entity and its version. Versioning is important when it comes to safely referencing entities in components. Because of the nature of ECS, entities usually get recycled. This is also the case in Fleks.

A tower-defense game is a good example to explain why versioning is useful. Imagine you have a tower that can target a single entity. This entity is stored in a component. If this entity now gets removed but in the same frame another entity is created (e.g. a special effect for the destroyed entity) then you will get an entity with the same id. However, the entity reference in the tower target component is now pointing at the newly created special effect entity. How does someone know if the referenced entity is still pointing at the same entity instance?

That's the point where versioning comes into play. Because now we have the information how many times an entity was recycled (=version). Every time an entity gets recycled, its version is increased. Therefore, a simple entity in world check is sufficient to solve the tower-defense problem from above. The system for the tower target just needs to check if the entity of the component is sill in the world. If yes then the entity can be attacked and if no then a new target needs to be retrieved. Here is how an entity looks like since Fleks 2.5:

data class Entity(val id: Int, val version: UInt) {
    companion object {
        val NONE = Entity(-1, 0u)
    }
}
Clone this wiki locally