Entities
This guide is currently incomplete A lot more info will be added soon to give a proper overview needed to get started with Geary.
Definitions
Notice how broad this definition is. An entity could be a zombie, a place in the world, the sound made by a player's footstep. What makes our entity unique is an identifier that represents it, in our case a 64-bit number we call EntityId.
These are not inherently related to each other, for instance an entity can have a location but no sprite if it is invisible. However, components are very useful together. With a sprite and location, we can render something on screen!
Tip: Each component should hold specific data, i.e. be good at one thing. This lets us choose exactly the data we need to write clean, modular code.
Syntax
Let's have a look at creating entities and giving them components.
// Create an empty entity
val entity: Entity = entity()
entity.id // Get the unique id of this entity
// Define a Location component (2)
class Location(val x: Double, val y: Double, val z: Double)
// Set(1) a location to our created entity
entity.set(Location(0.0, 10.0, 0.0))
// Do both at the same time (3)
val anotherEntity = entity {
set(Location(0.0, 0.0, 0.0))
}
// Read the data we set
entity.get<Location>() // returns Location?
- The
setoperation gives a component to our entity. We'll learn more about it shortly. - Notice this is just a regular class! We can add any class as a component, even if we didn't make it ourselves.
- This is the same as doing
#!kotlin entity().apply { ... }
Typealiases.
Geary provides typealiases like GearyEntity for most classes so you can avoid conflicts if you already have a class named Entity.
Entity operations
set gives an entity a component with data
entity.set(SomeData())
set takes a type parameter as a key. This is useful when working with another object-oriented application. For example, in Minecraft Player extends LivingEntity. We can set the player as a LivingEntity like so:
entity.set<LivingEntity>(player)
get reads a component of the given type
val data = entity.get<SomeData>() // returns SomeData? (1)
- Notice, the returned type is nullable. We need to handle the case when our entity doesn't have this component set.
add assigns a component type to an entity, without attaching data
entity.add<Alive>()
add is useful for marker components that don't need to store data. Later we will explore how this feature lets us create relations to other entities.
has checks whether an entity has a set/added component
entity.has<SomeComponent>() // returns Boolean
remove removes a set/added component of a given type
entity.remove<SomeComponent>()
with runs code if an entity has all requested components set
entity.with { loc: Location, sprite: Sprite, health: Health ->
println("I have all of $loc, $sprite, and $health on me!")
}
Null safety
Kotlin's null safety is extremely handy when trying to access components, because we are usually not aware of all the components an entity could have.
Null safety ensures we know what to do when a component isn't present. Here are some common use cases:
entity.get<A>() ?: return // (1)
entity.get<B>() ?: B() // (2)
entity.getOrSet<C> { C() } // (3)
- Tries to get
Aor stops if not present. - Tries to get
Bor uses a default value. - Tries to get
Cor sets and returns a default value.