I am currently creating a small hobby project to get back into game development, and I have decided to structure my entities using an ECS (Entity Component System). This implementation of an ECS is structured like so:
- Entity: In my case it's a unique
int
identifier that is used as a key to a list of components. - Component: Holds only data, e.g. the
Position
component holds anx
andy
coordinate, and theMovement
component holds aspeed
anddirection
variable. - System: Handles components, e.g. it takes the
Position
andMovement
components and adds thespeed
anddirection
to the position'sx
andy
coordinates.
This works fine, but now I wish to implement scripting into my games, in the form of a scripting language. In previous projects I've used an OOP implementation of game objects, which meant that scripting was pretty straight forwards. For example, a simple script could look something like this:
function start()
local future = entity:moveTo(pos1)
wait(future)
local response = entity:showDialog(dialog1)
if wait(response) == 1 then
local itemStack = entity:getInventory():removeItemByName("apple", 1)
world:getPlayer():getInventory():addItemStack(itemStack)
else
entity:setBehavior(world:getPlayer(), BEHAVIOR_HOSTILE)
end
end
However, when using an ECS, the entity itself does not have any functions like moveTo
or getInventory
, instead the above script written in ECS style would look something like this:
function start()
local movement = world:getComponent(MOVEMENT, entity)
movement:moveTo(pos1)
local position = world:getComponent(POSITION, entity)
local future = Future:untilEquals(position.pos, pos1)
wait(future)
local dialogComp = world:getComponent(DIALOG, entity)
local response = dialogComp:showDialog(dialog1)
if wait(response) == 1 then
local entityInventory = world:getComponent(INVENTORY, entity)
local playerInventory = world:getComponent(INVENTORY, world:getPlayer())
local itemStack = entityInventory:removeItemByName("apple", 1)
playerInventory:addItemStack(itemStack)
else
local entityBehavior = world:getComponent(BEHAVIOR, entity)
local playerBehavior = world:getComponent(BEHAVIOR, world:getPlayer())
entityBehavior:set(playerBehavior, BEHAVIOR_HOSTILE)
end
end
This is a lot more verbose compared to the OOP version, which is not desirable when the scripting is aimed towards mostly non-programmers (players of the game).
One solution would be to have some sort of wrapper object that encapsulates an Entity
and supplies functions such as moveTo
directly, and handles the rest internally, although such a solution seems sub-optimal since it takes a lot of work to cover all the components, and every time a new component is added you would need to change the wrapper object with new functions.
To all game developers that has implemented scripting in an ECS before - how did you do it? The main focus here is usability for the end user, with as little "maintenance" cost as possible (preferably you don't need to change it every time you add a component).
System
class/s to allow the components to remain data structs. \$\endgroup\$ – NeomerArcana Jul 22 at 23:05moveTo
method as a part of the underlying system in your use case, e.g. MovementSystem? This way not only then can you use it in the scripts you write but you can then also use it as a part of the C++ code too where you need it. So yes you'll have to expose new methods as new systems are added, but that's to be expected as its entirely new behavior these systems introduce anyway. \$\endgroup\$ – Naros Jul 23 at 14:07