Skip to content
Simon edited this page Dec 17, 2023 · 6 revisions

In case you need to iterate over entities with a specific component configuration that is not part of a system then this is possible via the family function of a world. A family keeps track of entities with a specific config and allows sorting and iteration over these entities. Family is used internally by an IteratingSystem. You can access it via the family property.

The following example shows how to get a family for entities with a MoveComponent but without a DeadComponent:

fun main() {
    val world = configureWorld {}
    val e1 = world.entity {
        it += Move(speed = 70f)
    }
    val e2 = world.entity {
        it += Move(speed = 50f)
        it += Dead()
    }
    val e3 = world.entity {
        it += Move(speed = 30f)
    }

    // get family for entities with a MoveComponent and without a DeadComponent
    val family = world.family { all(Move).none(Dead) }

    // you can sort entities of a family
    family.sort(compareEntityBy(Move))

    // And you can iterate over entities of a family.
    // In this example it will iterate in following order:
    // 1) e3
    // 2) e1
    family.forEach { entity ->
        // family also supports the typical entity extension functions like get, has, configure, ...
        entity.configure {
            // update entity components
        }
    }
}

Families also support FamilyHooks that allow you to react when an entity gets added to, or removed from a family. Such hooks can be created via the world's configuration. Here is an example:

fun main() {
    val world = configureWorld {
        families {
            // hook that reacts on entities that have a MoveComponent and do not have a DeadComponent
            val family = family { all(Move).none(Dead) }
            onAdd(family) { entity ->

            }
            onRemove(family) { entity ->

            }
        }
    }

    // this will trigger the 'onAdd' hook
    val entity1 = world.entity {
        it += Move()
    }
    // this will trigger the 'onRemove' hook
    world -= entity1

    // this will NOT trigger any hook because the entity is never part of the family
    val entity2 = world.entity {
        it += Move()
        it += Dead() // <-- that's the reason
    }
    world -= entity2
}

Since Fleks 2.6 there is also a new way to create a FamilyHook for an IteratingSystem. There are two interfaces:

  • FamilyOnAdd: a system with this interface must implement a onAddEntity method.
  • FamilyOnRemove: a system with this interface must implement a onRemoveEntity method.

Systems that are implementing any of those two interfaces are detected automatically during world configuration and a family hook for the system's family will automatically be created. It is possible to have multiple systems iterate over the same family. Their onAddEntity / onRemoveEntity methods are called in the order of the systems of the world. If you also have a family hook defined in the world's configuration block then the logic of this hook will be called before the systems' hooks.

It is important to note that this feature does not add anything new. You can achieve the same thing with the world's family hook configuration. This is just a convenience feature to make it easier to define a hook for a system's family.

data class Move(var speed: Float) : Component<Move> {
    override fun type() = Move

    companion object : ComponentType<Move>()
}

class System1 : IteratingSystem(family { all(Move) }), FamilyOnAdd {
    override fun onAddEntity(entity: Entity) {
        // ...
    }
}

class System2 : IteratingSystem(family { all(Move) }), FamilyOnAdd {
    override fun onAddEntity(entity: Entity) {
        // ...
    }

}

fun main() {
    val world = configureWorld {
        systems {
            add(System1())
            add(System2())
        }
    }

    // creating an entity will call System1's onAddEntity first, and then System2's onAddEntity
    world.entity { it += Move(speed = 3f) }
}
Clone this wiki locally