Game Engine Overview
The game uses a modified entity component system, ecs, based mainly on the ideas from Alex Kehayias presentation Functional Game Engine Design for the Web. It's recommended to watch this.
The game state is a single hashmap containing other clojure data structures. State transitions are functions from one game state to another. State transition functions are composed of systems (see below). Since clojure data structures are persistent we could get some of the benefits of the flyweight pattern without any effort.
See Flyweight Pattern and Clojure Data Structures
List of concepts used in engine
Entities
Entities are objects in the game world. Each entity has a collection of components attached to the entity.
Components
Components store component-state for each entity with that component attached. Component-state is implemented as a hashmap.
Systems
Systems are functions that take the game state hashmap and a vector of events as an arguments and returns a pair, the updated game state and events that should be sent. Which systems should be used to get the next gamestate is specified by scenes.
Systems are connected to a type of component. They iterate over all entities. If an entity has a component attached the system may calls a component-function or perform an some action involving the entity.
Systems are also Observers. They can subscribe to events, reciving all events of a specific type. They can subscribe to multiple types of events at the same time.
Component-functions take the component-state and events as an arguments and returns a pair containing the new component-state and new events.
Scenes
A Scene is a vector of systems that should be ran with the game state in the specified order to get the next game state. Exactly one scene is active at any given moment.
Events
Events are used to send information between different component. An event consists of an event-id and a hashmap (possibly vector in the future) containing the event-arguments. They can be sent by systems to other systems. If an observer recives an event or not is decided by the events type.
Local events that can only be recived by the entity sending it should maybe be added.
Services
Asynchronously executes actions that do not need to be synced with the rest of the engine.
If a sound system is added it should probably be a service.
question: can player IO be implemented as a service?
(Services are not a priority right now).
See Service Locator
Instance map
Some kind of instance map containing information of what entities are located at a position is needed to allow* entities to interact with each other based on their location in the game world.
(* allow them to do it efficiently without looping through every entity way too many times)
Game State Hashmap
The game state is implemented as a clojure hashmap.
Keys
:entities
Hashmap from entity-id to set of component-ids.
:systems
Hashmap from system-id to system function.
:scenes
Hashmap from scene-id to vector of system-ids.
:active-scene
Scene-id of currently active scene.
:state
Nested hashmap where the path [:component-id :entity-id]
contains the state
of a component in the entity.
:subscriptions
Hashmap from system-id to collection of event-types/event-ids. Should maybe be
under :system
key instead.
:events
Hashmap from system-id to vector of events sent to system. The vector should be empty after the corresponding system was called.
Example State:
{:entities {:player #{:move :print-state}} :systems {:state-printer (get-system-fn :print-state print-state) :movment (get-system-fn :move move)} :scenes {:default [:movment :state-printer]} :active-scene :default :state {:move {:player {:x 0 :y 0}} :print-state {:player {:printed-state "state-thing"}}} :subscriptions {:movment #{:move-entity}} :events {:movment []}}