| engine | ||
| test | ||
| .dir-locals.el | ||
| .gitignore | ||
| all.options | ||
| LICENSE | ||
| README.org | ||
Imugi
Imugi is a framework for building games (primarily tactics games) with Chicken Scheme and raylib. The end goal of this framework is to create a game, also called Imugi, and release to show the capability here.
Architecture
Imugi is an ECS engine.
Entities
Entities are game objects stored in an SRFI-69 hash table. A hash table is used for O(1) lookup.
Entity keys within the hash table are symbols, either set intentionally or automatically generated via gensym.
Entities are identified by their key, and are each a list of components.
Entities are queued for addition and removal to/from the hash table world.
Entity removal and addition is performed at the beginning of each frame, with removal occuring first.
There exists a hash map of SRFI-113 sets, component-sets.
When an entity is added to world, every component of the entity is checked and the name of each component record is retrieved.
The symbol representing the entity in world is then added to each set in the hash map associated with a component record type that the entity owns.
This way, systems can query entities by the combination of components, using union operations.
When an entity is removed from world, it is also removed from every component set.
Components
Components are SRFI-99 records which store data associated for a particular entity.
Systems
Systems are functions which modify state and draw to the screen.
Systems are stored in the systems list as a record containing a name (symbol), a priority (integer), entity criteria (list of symbols), and the function itself.
Each system specifies the components an entity (the entity criteria) must have for it to be processed by that system. The entities are then selected for the system from the component-sets.
Each system is applied to all entities which match its entity criteria.
Systems have a priority which defaults to 0. Lower priority systems will be executed before higher priority systems. Systems within the same priority execute in order of addition to the systems list.
The systems list is sorted on each addition and removal of a system.
Like entities, the addition of systems is queued for the beginning of each frame. This prevents the system list from being sorted multiple times per frame.
Systems can access resources via free variables or by being formed as closures. Systems only ever take an entity (a component list) as input.
Event Buses
An event bus is a hash table of symbols and records. They allow for delegating changes sent from other objects.
The event record can contain any arbitrary data. The symbol the record is associated by is unique within the association list.
Event records stay in the bus until removed by another system.
Systems can push and pull events to and from the bus.
Systems can choose to either pop the event off the bus (removing it from further processing) or just peek at the event.
The symbol an event is associated by is essentially an "action". The event bus can have multiple events of the same type (for example, multiple different key presses can occur in a frame). Only one event of each name (for example, only 1 event can be called "jump", which might correspond to a space key press) is allowed in the bus at any one time.
Custom event buses can be created and stored in the event-buses hash table. Systems can query a particular event bus within that list for a particular event.
Rendering
Imugi maintains 3 render queues, with one each for screen, 2D and 3D drawing.
New queues can be added by adding a new list to the render-queues hash table.
Systems can push render objects to any of these queues, which are simply pairs of integers and a lambda thunk.
The initial integer denotes a layer, while the lambda thunk contains the drawing function to evaluate.
When drawing the next frame, the system iterates and executes every drawing thunk in order from the smallest to the largest layer.
The 3D queue is executed first, followed by the 2D queue, followed by the screen queue.
The order of execution can be changed by modifying the render-priority list.
The render-priority list contains pairs where the first symbol in the pair is a reference to the render queue in the render-queues hash table, and the second symbol is either screen, 2d, or 3d. The order of items in this list informs Imugi of the order in which to execute the queues, and the second symbol of each pair tells Imugi what draw mode to use for each thunk.
For 2D and 3D drawing, systems can set the currently active camera object on the parameters *active-camera-2d* and *active-camera-3d*. If a 3D or 2D render is queued but no corresponding camera for the mode is set, the render will not occur but the render queue will still be emptied.
Resources
When a resource, such as a font or texture, is loaded from the filesystem, the pointer is stored in an SRFI-69 hash table resources keyed by the path.
When records are first loaded, a finalizer is set for them ensuring they are unloaded when collected by the GC.
If the game attempts to load a resource which has already been loaded, it will use the pointer in the resources hash table instead.
Folder Structure & Module Documentation
The engine folder contains core engine code, organised across the following modules:
The games folder contains various sample games made with Imugi.
(engine core)
(engine core) contains the fundamental engine functions. Entire games can be made using just the core module, and all other modules simply extend this module.
Game State
world is an SRFI-69 hash table, which contains all entity component lists keyed by their symbol name.
component-sets is an SRFI-69 hash table, which contains an SRFI-113 set for every component type in the world. Each set contains a symbol referencing a world entity that has a component of that type.
systems is a list, which contains all lists to be executed on each frame.
event-buses is an SRFI-69 hash table, which contains hash tables keyed by the event bus name. Each hash table in event-buses keeps track of individual event records keyed by action names.
render-queues is an SRFI-69 hash table, which contains lists keyed by the render queue name. Each item in these lists is a pair, where the first element is the layer of the drawing and the second is a thunk that gets called during the render.
render-priority is an association list, where the first element of each pair is a render queue name from the render-queues hash table, and the second element is the drawing mode (a symbol which is either screen, 2d, or 3d). The order of elements in the render-priority association list determines the order in which the render-queues are evaluated, with the first queue name in the list being the first queue to evaluate.
Entities
(create-named-entity id . components)
(create-entity . components)
(remove-entity id)
(get-entity id)
(clear-world)
Functions for creating, removing, and fetching entities. id must be a symbol, and each parameter passed in as components must be an SRFI-99 record.
When entities are created or removed, the creation/deletion is added to an internal queue. Entity creation and removal to/from the world state from the queue is performed at the top of each frame, or when (resolve-queues) is executed.
Systems
(make-system name priority criteria process)
(system? record)
(system-name system)
(system-priority system)
(set-system-priority! system priority)
(system-mode system)
(system-criteria system)
(set-system-criteria! system criteria)
(system-process system)
(set-system-process! system process)
Functions for creating systems, which are SRFI-99 records. name must be a symbol, priority must be an integer, mode must be either entity or batch, criteria must be a list of symbols, and process must be a single argument procedure (where the single argument is expected to be either a single entity matching all given criteria when the mode is entity, or a list of entities matching the given criteria when the mode is batch).
(add-system system)
(remove-system name)
(clear-systems)
Functions for adding and removing systems from process. system must be an SRFI-99 record, and name must be a symbol (which is used to identify a system record in the systems list).
When systems are created or removed, the creation/deletion is added to an internal queue. System creation and removal to/from the systems list from the queue is performed at the top of each frame, or when (resolve-queues) is executed.
Event Buses
(register-event-bus name)
(remove-event-bus name)
(fetch-event-bus name)
Functions for creating, removing, and retrieving event buses. name must be a symbol referencing the bus name in the event-buses hash table.
Event buses are added and remove immediately, without queuing.
(push-event bus action event)
(peek-event bus action)
(pop-event bus action)
Functions for creating and fetching events. bus must be a symbol referencing a bus name, action must be a symbol referencing the name of the action, and event must be a record.
peek-event returns the event record but keeps the event in the event bus. pop-event returns the event record and removes it from the event bus.
Frame Generation, Render Queues, and Game Loop
(register-render-queue queue-name drawing)
Create a new render queue in render-queues, where queue-name is the queue name symbol and drawing is the drawing mode (either screen, 2d, or 3d). Render queues created this way are automatically placed at the front of the render-priority assocation list.
(push-render-object queue-name layer thunk)
Adds a thunk to the given render queue name at the given layer. queue-name must be a symbol, layer must be an integer, and thunk must be a zero-argument procedure.
*active-camera-2d*
*active-camera-3d*
Parameters which describe the currently active camera for 2D and 3D drawing.
*clear-color*
*clear-color* is a parameter which expects a u8vector corresponding to a Raylib color.
(evaluate-render-queue queue-name mode)
(perform-render)
Functions for evaluating the render queues. evaluate-render-queue evaluates a single queue where queue-name is a symbol corresponding to a queue in the render-queues, and mode is a drawing mode symbol (either screen, 2d, or 3d). The given queue is cleared after evaluation, and each thunk in the render queue is evaluated with the appropriate mode, using the *active-camera-2d* and *active-camera-3d* for the 2d and 3d drawing modes.
perform-render clears the screen to the set *clear-color*, then iterates the render-priority association list and calls evaluate-render-queue on each queue in order with the appropriate mode.
(resolve-queues)
(resolve-queues) causes all queues to immediately resolve pending actions to the true game state.
(next-frame)
(next-frame) is a frame generation function. When called, it resolves all queues, then executes all systems, then finally performs the render of the next frame.
Window Functions
*window-size*
*window-title*
*target-fps*
*window-size* is a parameter that expects a cons pair of two integers, where the first integer is the window width, and the second is the window height.
*window-title* is a parameter that expects a string, which is used for the window title.
*target-fps* is a parameter that expects an integer, which specifies the desired frames per second to run at.
(create-window process: (next-frame) close-predicate: (window-should-close?))
create-window creates a window using the window parameters described above, sets the target frames per second, then enters a loop which runs the process: function (next-frame by default) on each frame, unless close-predicate: (Raylib's window-should-close function by default) returns true, in which case the window is closed.
Dependencies
The following Chicken dependencies are required:
- raylib
- SRFI-99
- SRFI-113
- SRFI-69
- csm
- SRFI-78
Running
From the build directory, run csm -program test.engine .. && ./test.engine to run engine tests.