Geary docs

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?
  1. The set operation gives a component to our entity. We'll learn more about it shortly.
  2. Notice this is just a regular class! We can add any class as a component, even if we didn't make it ourselves.
  3. 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)
  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)
  1. Tries to get A or stops if not present.
  2. Tries to get B or uses a default value.
  3. Tries to get C or sets and returns a default value.