module Ent:sig
..end
typeid =
int
type ('component_type, 'component)
entity
entity / component
concept
the component_type
is the key to access to the components
val new_entity : unit -> ('comp_t, 'comp) entity
val get_id : ('comp_t, 'comp) entity -> id
val get_id_opt : ('comp_t, 'comp) entity -> id option
get_id
but returns None instead of raising an exception
An entity may have as many components as needed, but only
one component of each type.
val add_component : ('comp_t, 'comp) entity -> 'comp_t * 'comp -> ('comp_t, 'comp) entity
add_component e (component_type, component_data)
val replace_component : ('comp_t, 'comp) entity ->
'comp_t -> 'comp -> ('comp_t, 'comp) entity
replace_component e component_type component_data
val has_component : ('comp_t, 'comp) entity -> 'comp_t -> bool
val has_components : ('comp_t, 'comp) entity -> 'comp_t list -> bool
(this function does not tell if the entity contains other components
or not)
val has_any_component : ('comp_t, 'comp) entity -> 'comp_t list -> bool
val get_component : ('comp_t, 'comp) entity -> 'comp_t -> 'comp
Not_found
if none foundval get_component_opt : ('comp_t, 'comp) entity -> 'comp_t -> 'comp option
None
if has not the componentval remove_component : ('comp_t, 'comp) entity -> 'comp_t -> ('comp_t, 'comp) entity
val iter_components : ('comp_t, 'comp) entity -> ('comp_t -> 'comp -> unit) -> unit
val get_components : ('comp_t, 'comp) entity -> 'comp_t list
val cmp_components : ('comp_t, 'comp) entity -> 'comp_t list -> int
val components_match : ('comp_t, 'comp) entity -> 'comp_t list -> bool
*component*
functions
you may simplify your life, your code and save time by using
the gen_comp
command line tool that generates all the constructors,
accessors and modifiers from the type definition of 'component
.
For example create a file "comp.ent"
with:
Alpha int
Beta float
Gamma string
Delta char
Epsilon bool
then generate the constructors, accessors and modifiers (and also printing functions for debuging) with:
ocaml gen_comp.ml -ml comp.ent > comp.ml
ocaml gen_comp.ml -mli comp.ent > comp.mli
See the HOWTO
section at the end for further informations.
type ('comp_t, 'comp)
world
component_type
the second is the component
for the third one see the doc of the function world_step
and see world_step_fold
about the 'fld
type
val new_world : unit -> ('a, 'b) world
val add_entity : ('a, 'b) world -> ('a, 'b) entity -> ('a, 'b) world
val add_entities : ('a, 'b) world -> ('a, 'b) entity list -> ('a, 'b) world
val add_entity_id : ('a, 'b) world -> ('a, 'b) entity -> ('a, 'b) world * id
add_entity
but also return the id that was given to this entityval add_entities_id : ('a, 'b) world ->
('a, 'b) entity list -> ('a, 'b) world * id list
val add_entities_init : w:('a, 'b) world ->
n:int -> f:(int -> ('a, 'b) entity) -> ('a, 'b) world
val has_entity : ('a, 'b) world -> id -> bool
id
val replace_entity : ('a, 'b) world -> id -> ('a, 'b) entity -> ('a, 'b) world
val remove_entity : ('a, 'b) world -> ('a, 'b) entity -> ('a, 'b) world
val remove_entity_id : ('a, 'b) world -> id -> ('a, 'b) world
val get_entity : ('a, 'b) world -> id -> ('a, 'b) entity
val get_entity_opt : ('a, 'b) world -> id -> ('a, 'b) entity option
val get_entities : ('a, 'b) world -> id list -> ('a, 'b) entity list
val do_get_entities : ('a, 'b) world -> id list -> ('a, 'b) entity list
Not_found
if an id is not foundval get_entities_with_components : ('comp_t, 'comp) world ->
'comp_t list -> ('comp_t, 'comp) entity list
mapper
val iter_entities : (('a, 'b) entity -> unit) -> ('a, 'b) world -> unit
val fold_entities : (('a, 'b) entity -> 'p -> 'p) -> ('a, 'b) world -> 'p -> 'p
val num_entities : ('a, 'b) world -> int
val num_entities_with_components : ('comp_t, 'b) world -> 'comp_t list -> int
type 'component_type
mapper
val add_mapper : ('comp_t, 'b) world ->
'comp_t list -> ('comp_t, 'b) world * 'comp_t mapper
system
may update or remove entities, or create new ones, each time
world_step
is called.type 'a
update =
| |
Identical |
(* | no components changed | *) |
| |
Updated of |
(* | the value of some components where updated | *) |
| |
Replace of |
(* | some components were added and/or removed | *) |
| |
Removed |
(* | remove this entity | *) |
type('comp_t, 'comp, 'delta)
system =('comp_t, 'comp) entity ->
('comp_t, 'comp) world ->
'delta ->
('comp_t, 'comp) entity update * ('comp_t, 'comp) entity list
type('a, 'b, 'delta, 'fld)
foldable_system =('a, 'b) entity ->
('a, 'b) world ->
'delta ->
'fld -> ('a, 'b) entity update * ('a, 'b) entity list * 'fld
system
but a foldable_system
has an additional folded
parameter that can be given with world_step_fold
type('comp_t, 'comp, 'fld)
born_feedback_func =('comp_t, 'comp) world ->
('comp_t, 'comp) entity ->
id list -> 'fld -> ('comp_t, 'comp) world * 'fld
system
signature)val world_step : ('a, 'b) world ->
('a, 'b, 'delta) system -> 'a mapper -> 'delta -> ('a, 'b) world
'delta
is the input parameter that is given to the systemsval world_step_fold : ('a, 'b) world ->
('a, 'b, 'delta, 'fld) foldable_system ->
'a mapper ->
?fb:('a, 'b, 'fld) born_feedback_func ->
'delta -> 'fld -> ('a, 'b) world * 'fld
world_step
but with an additional folded parameter 'fld
,
see foldable_system
sgen_comp
helper tool:type component_type =
| Level_t
| Name_t
type component =
| Level of int
| Name of string
let make_entity level name =
let e = Ent.new_entity () in
let e = Ent.add_component e (Name_t, Name name) in
let e = Ent.add_component e (Level_t, Level level) in
(e)
let get_level e =
match Ent.get_component e Level_t with
| Level level -> level
| _ -> failwith "get_level"
let bump_system_comps = [Level_t]
let bump_system e w bump =
let level = get_level e in
let e = Ent.replace_component e Level_t (Level (level + bump)) in
(Ent.Updated e, [])
let name_string e =
match Ent.get_component_opt e Name_t with
| Some (Name name) -> Printf.sprintf "name=%s" name
| _ -> ""
let level_string e =
match Ent.get_component_opt e Level_t with
| Some (Level level) -> Printf.sprintf "level=%d" level
| _ -> ""
let () =
let e1 = make_entity 1 "dummy-A" in
let e2 = make_entity 6 "dummy-B" in
let w = Ent.new_world () in
let w = Ent.add_entities w [e1; e2] in
let w, bump_system_mapper = Ent.add_mapper w bump_system_comps in
let w =
Ent.world_step w
bump_system
bump_system_mapper
1
in
Ent.iter_entities (fun e ->
Printf.printf "Entity: %s %s\n" (name_string e) (level_string e)
) w
Running this example:
$ ocaml -I $ENT_DIR ent.cma example.ml
Entity: name=dummy-A level=2
Entity: name=dummy-B level=7
This example uses only this module.
Even with a small example like this one, you can see that the functions
to set and get the component values are not very elegant.
This is why there is the gen_comp
command line tool to generate
functions to simplify components management.
Read the following Howto to see how to do this.
Comp
module to manage the components.
You can generate the constructors, accessors and modifiers
from the type definition of the component
s.
This type definition is not written in ocaml, but in a simplified way.
Here is an example with the file "comp.ent"
:
$ cat comp.ent
Position xy
Velocity xy
You need to define each type of this *.ent
file,
and string_of_*
functions:
$ cat comp_before.mli
type xy = float * float
$ cat comp_before.ml
type xy = float * float
let string_of_xy (x, y) =
Printf.sprintf "(%g, %g)" x y
Generate and compile this Comp
module
using the provided gen_comp
command line tool:
cat comp_before.mli > comp.mli
cat comp_before.ml > comp.ml
ocaml gen_comp.ml -mli comp.ent >> comp.mli
ocaml gen_comp.ml -ml comp.ent >> comp.ml
ocamlc -c -I $ENT_DIR comp.mli
ocamlc -c -I $ENT_DIR comp.ml
Now you can write your main code:
let fps = 30 (* frames per second *)
let dt = 1.0 /. float fps (* elapsed time between 2 frames *)
(* make n loops *)
let rec loops n f w =
if n <= 0 then w else
let w = f w in
loops (n-1) f w
let move_entity (x, y) (vx, vy) dt =
(x +. vx *. dt,
y +. vy *. dt)
(* define which components needs moving_system *)
let moving_system_comps =
[Comp.Position_t; Comp.Velocity_t]
let moving_system e w dt =
let pos = Comp.get_position e in
let vel = Comp.get_velocity e in
let new_pos = move_entity pos vel dt in
let e = Comp.update_position e new_pos in
Ent.Updated e, []
let make_entity pos vel =
let e = Ent.new_entity () in
let e = Comp.add_position e pos in
let e = Comp.add_velocity e vel in
(e)
let () =
let e1 = make_entity (2.0, 3.0) (0.1, 0.1) in
let e2 = make_entity (5.0, 6.0) (0.1, 0.2) in
let w = Ent.new_world () in
let w = Ent.add_entities w [e1; e2] in
let w, moving_system_mapper =
Ent.add_mapper w moving_system_comps
in
let step w =
Ent.world_step w
moving_system
moving_system_mapper
dt
in
let w = loops fps step w in (* loop for 1 second *)
Comp.print_entities w
Run this code:
$ ocaml -I $ENT_DIR ent.cma comp.cmo howto.ml
Entity:
Position_t Position (2.1, 3.1)
Velocity_t Velocity (0.1, 0.1)
Entity:
Position_t Position (5.1, 6.2)
Velocity_t Velocity (0.1, 0.2)
gen_units
.
It generates the code of a function to create each unit from a simple description file.
This description file has one unit description by line. Each line contains several elements separated by tabulations. The first element of the line is the unit name. All the following elements define the components of this unit.
Here is an example:
my_item Position Velocity
Generate the code from this unit description using the
gen_units
command line tool:
$ ocaml gen_units.ml units.def > units.ml
Here is the generated function:
val make_my_item : Comp.xy -> Comp.xy -> Comp.entity
The generated code of this function is:
let make_my_item position velocity =
let e = Ent.new_entity () in
let e = Comp.add_position e position in
let e = Comp.add_velocity e velocity in
(e)
The generated Comp
module (see the previous section)
needs to be compiled before this Units
module.
More details for units description
To create labels for the parameters of the generated functions:
static_item pos:Position
moving_item pos:Position vel:Velocity
Here the function won't have any parameter for velocity,
and the function will add a Velocity
component with the
given value:
falling_item pos:Position Velocity=(0.0, -0.2)
This given value may be a function call:
random_item Position=(rand_pos ()) Velocity=(rand_vel ())
Parameters with a default value:
item_defaults ?pos:Position=(0.0, 0.0) ?vel:Velocity=(0.0, 0.0)
If initialisation functions are used you can put these in
an other file units_init_funcs.ml
and compile everything (after
the Comp
module) like this:
$ echo "open Units_init_funcs" > units.ml
$ ocaml gen_units.ml units.def >> units.ml
$ ocamlc -c units_init_funcs.ml
$ ocamlc -c -I $ENT_DIR units.ml