Skip to content

Game objects in KAPLAY

Game objects are the basic building blocks of your game. They are the objects that you will be interacting with. They are composed from components, so each object can have the functionality it needs.

Start out by creating a new type in a module:

// You can use a nested module or a separate file
module Hero = {
type t
}

Next included the components via the include module syntax.

module Hero = {
type t
open Kaplay
// We want to draw an image on the screen for an object
include Sprite.Comp({type t = t})
// We want to change the position of the object
include Pos.Comp({type t = t})
}

Including a composition will add additional functions to our Hero module. These can be getters and setters for the component state or a function create the corresponding comp instance.

Lastly, we need a way to create our game object, so we include a make function:

module Hero = {
type t
open Kaplay
include Sprite.Comp({type t = t})
include Pos.Comp({type t = t})
let make = (k: Context.t) : t => {
// `Context.add` takes a list of `comp` and returns a generic object.
// This is why we annotate the return function with the type `t`.
k->Context.add([
k->addSprite("hero", ~options={width: 30., height: 30.}),
k->addPos(100., 100.),
])
}
}

To create a game object we call the make function:

let hero = Hero.make(k)

Note that we pass in the Game context to the make function. We could also open the module that has our context and use it as an import.

Because we included Sprite and Pos composition, we can now access additional functions via hero->Hero..

let pos = hero->getPos
hero->play("walk")

So far, our type t in our Hero module has been abstract. We could, however, using a record instead to store some state.

module Hero = {
type t = { name: string }
external initialState: t => Kaplay.Types.comp = "%identity"
include Pos.Comp({type t = t})
let make = (k: Context.t) : t => {
k->Context.add([
addPos(k, 4. , 3.),
initialState(~name="Freddie")
])
}
}

The initialState function is a special function that is used to create the initial state of the object.

Sometimes you might be tempted to create a game object inside another game object. For example a health bar inside a player object.

open Kaplay
module Player = {
type t
include Pos.Comp({type t = t})
module HealthBar = {
type t
include Sprite.Comp({type t = t})
let innerMake = (k: Context.t) : t => { ... }
}
let make = (k: Context.t) : t => { ... }
}

The danger here is that the innerMake function will have access to Pos.Comp functions, because of the parent module. HealthBar.getPos will be available, but won’t exist at runtime. Thus, it is best to avoid nested game objects.