Recently I have been researching about the ECS pattern and its uses in game development, and decided to try it out as it looks like a nice concept which can be extremely powerful if implemented correctly, though I am having trouble integrating it in my personal project.
I experimented with some implementations (of third parties and a custom made one) on some local files to get a “feel” of how the pattern works, but I’m not sure on how to do it on the game scale, such as how to handle players, player interaction and player data as component-systems - and especially, how to split systems between the client and the server.
Until now, I kept the client and server code completely separated from each other (sometimes both using different architectures) and relied solely on remotes for transmission of data, but I want to integrate them more closely so I do not have to recode every new feature I add twice (also minimizing bugs in this aspect) - this is an alien concept to me and I never tried it before.
So, I am looking for example implementations as looking at examples usually helps me clear my doubts. My game is not round-based, and has in general a more open-world end, and I also do not have much experience with that. With that said, in sum:
How did you implement entity systems for your multiplayer, open-world games?
How do you manage server-client communication in your systems?
Should I stick with a pure implementation of an ECS pattern or is it not worth it, and should I have other designs such as OOP too?
Is something like this what you want?
I’ve never heared of ECS before so I used wikipedia and based it on the information the wiki gave me, sorry if this isn’t what you wanted.
–module script
local entities = {}
local private = {}
local _curId = 0
function entities:CreateNew(stats)
_curId = _curId + 1
local id = _curId
private[id] = {}
local cur = private[id]
cur.uniqueId = id --entities unique id.
for k,v in pairs(stats) do --adds all of the stats into this entity
cur[k] = v
end
return cur
end
function entities:ReplaceId(id,nid) --changes id (requeres old id)
if private[id] then
_curId = _curId + 1
private[id].uniqueId = _curId
return private[id]
else
warn("Given ID does not exist, returning nil.")
return nil
end
end
function entities:ChangeId(entity,nid) --changes id (requires the entity)
if entity.uniqueId then
_curId = _curId + 1
private[entity.uniqueId].uniqueId = _curId
return private[_curId]
else
warn("Given entity does not exist, returning nil.")
return nil
end
end
function entities:GetEntity(id) --returns the entity if you only have the id
if private[id] then
return private[id]
else
warn("Given ID does not exist, creating new entity with no stats.")
return private[entities:CreateNew()]
end
end
return entities
--server script
local mobhandler = script.Parent.ModuleScript
local e = require(mobhandler) --the module script
--mob stats
local mob = {}
mob.speed = 16
function mob:SayHi(s)
print(self.uniqueId.." said: Hi")
end
--creates 2 mobs
local mob1 = e:CreateNew(mob)
local mob2 = e:CreateNew(mob)
mob1:SayHi() --> 1 said: Hi
mob2:SayHi() --> 2 said: Hi
As for server-client communication, I would make remote- functions/events and use that to keep everything synced up.
I’ve actually recently started work on an ECS library that I hope to finish soon.
I can’t answer the first two questions yet as I haven’t worked on my Client and Network Systems. However, as to the third, it’s not really possible to go with a pure ECS approach, I tried my best to stick to it, though.
When I finish my library I’ll post it on the Community Resources category, and reply to this answer with the link.
I’m trying to experiment with different paradigms than OOP, and thought it would be a fun little project to try to make.
The game itself is an open-world game (ie, not round based), in which the players individually can do different activities by themselves or with their friends in an enclosed map, with certain interactions only being available if you are close to certain objects.
Now that I think about it, the only thing that isn’t really pure ECS, is how I added behavior directly to Entities. Which is just adding and removing components, getting the amount of components, and getting all components, a function to check if it has a specific component(s), and stuff like that.
While ECS and OOP solve the same problems in the end, ECS is not sealed in OOP as you say.
ECS was designed to be data oriented, not object oriented. The concept of ECS was to avoid messy inheritance hierarchies where a certain class wouldn’t very well fit, and just added onto complications, performance issues, and maintenance (again to the messy inheritance hierarchies).
And while you can do a OOP ECS approach, pure ECS has more pros than cons (in my opinion).
My only concern is that you don’t write a system you don’t need. I spent I think about 3 years of dabbling in game development working on a custom ECS, just for it to not be needed or really used.
I would caution you to illustrate that the mechanics you want to implement need to be modular enough that a ECS would be a valuable investment of your time.
If you want both the server and client to use the same functionality (eg. You want the client be able to determine whether something is a bomb), you can create ModuleScripts with your functions and put them into ReplicatedStorage, then you can use them in your server and client scripts.