Structuring Entities without ECS

Hello everyone,
I’m currently starting to experiment with possible rules I could build an RTS game with.
For instance, I’ve planned to add buildings which can be built and destroyed.
Different buildings bear different effects for the player which owns said building.

To structure this, according to what I understood is best practice in jME,
I would create a BuildingFactory whose create() would take, to keep it simple, only the building type (house, farm, etc.).
The create() method would instantiate a new Node and attach attributes via setUserData and give behaviour via custom Controls, which are added using addControl().

Now my question is: How do you guys go about structuring your composed entities?
I thought of sticking to a Factory pattern and construct a full entity that way.
Doing this, I would always add certain UserData which every entity should have (name, absolute location) plus any additional, entity-specific data. That would at least make the process fully compositional.

The problem I’m also facing dumbfound as of now is how to handle the actual appearance - just use a custom control with name, say, “ModelControl” and a string argument for the exact model-file to use? Seems a bit wrong this way, since Controls call the update() method so frequently, yet the model only needs to be loaded once. An AppState seems to broad though, as it isn’t focused to a particular Spatial. Halp!

I guess what I’m trying to do here is start a discussion on how else this problem could be solved, or has been solved by members of the forum. Please let me know how you handle entities, without direct usage of ECS.

1 Like

If you really dont want use ECS, then i would suggest Store Game Objects in some Map/List and manage them with AppStates.

Lets say you use just OOP(no ECS), so you just make GameObject.class that is extended by Tank.class and Factory.class and store them in some LIST/MAP, so your appstates check what it is. If Its Factory it can do something, if its Tank, it can do something else. Spatial links can be stored in This Object, so you no need “setUserData” in this case.

Anyway i would suggest using ECS.

1 Like

Whether you use an ECS or not, ultimately you will want to think of Spatials as “visual objects” and have separate game objects.

…I mean, as soon as you want to save your game, you’re probably going to want to do something other than save every mesh, texture, etc. into your savegame file. Then if you update your game and the player loads that save game file they still have all of the old meshes, textures, etc…

Using spatials as game objects is ok for simple and/or toy games but as soon as you want to do anything real it’s best just to use game objects.

If it’s helpeful, here are two (networked) examples of the same game, one using regular game objects and the other using an ECS:

POJO game objects:

ECS game objects:

For something like an RTS, I personally would undoubtedly without any hesitation what-so-ever use an ECS. But I’ve already climbed that learning curve years ago (and wrote the Zay-ES library as by-product), so I’m pre-biased.

Edit: is there a reason you want to avoid ECS other than learning curve? Just making sure we are all on the same page. (Learning curve is a significant enough reason but some folks think that maybe JME discourages ECS… which is definitely not the case.)

3 Likes

This is basically the approach I follow since I don’t use an ECS and follow a mostly OOP approach. I haven’t run into any issues with this style so far while working on an RPG, but I also haven’t used the alternative to know for sure if I like OOP for game dev better than an ECS or if I’m just too lazy to learn something different since what I’m doing seems to be working.

To avoid using controls, just make a list of your GameObjects in a central GameState.class and you can loop through that list to manually call an update method in your own GameObject classes, and then store a Spatial variable in the GameObject class (along with an ID and all the other important variables for that type of game object) so then you aren’t relying on controls to manage a spatial that has all the data stored as userData.

I personally only use userData for flagging spatials and nodes in the scene editor so I know if it should do things like turn into a more complex item or spawn point for NPCs, or if the model should get physics or ignore collisions with the camera (in cases of invisible barriers).

2 Likes

I find it hard to reply to each response individually, so please don’t take offense if I reply to everyone at once.
Reading everything posted, I find it harder to resist reading more into ECS.
My understanding of ECS, as of now, is having a System which acts as a container for entities. Those entities, in OOP terms, would be composed of individual Components, which house functionality. The entities themselves are basically shells for the components to make the entity what it is. But this seems to contradict what people say about ECS being different from OOP.
In essence, I need to learn more about ECS and get a grasp on how to build the most simple ECS myself to get hands-on experience with this (to me) new system.

/edit:
And by ‘building a simple ECS’, I mean the actual logic that drives an ECS, not using an existing ECS implementation. Personally, I benefit from code-based explanations a lot more than just the pure abstract concept of things. Although to be fair, I still have to understand the abstract concept of an ECS in its entirety.

1 Like

yes, each System have “assigned”(based on required component types for system) Entites into some Tracks Lists. It all depends how ECS is written, but i belive many ECS-libs allow Systems to create Track Lists to track Entity changes/triggers and just to know their list.

myEntityHealthComponent.health -= 10;
myEntityHealthComponent.applyChanges() // send trigger that entity component changed
// HealthSystem knows that need update for that Entity since it changed(via some track list lets say), so it receive trigger and execute its work related to health things

dont know how to explain it in more simple way. Wiki explain it well enough anyway. (it is abstract since many ECS libs can solve things little differently)

well, ECS is different than OOP i belive mostly because you write “system procedural code for Entity managment” and “do not extend Components for additional methods”. Other things can be OOP, but idea itself dont seem to be OOP, more like procedural. Also Entites are not extended like in OOP but they have Components that extends their functionality.

Components here are just “data storages” in reality, not OOP related.

1 Like

Yeah, at it’s simplest: entities are just containers for data. Systems are just code that operates on those.

If those network examples were too complicated then here is a simpler non-networked example… a full asteroids game, ECS based:

Edit: and as author of the Zay-ES library and an ECS evangelist for almost a decade, I can talk on this subject for hours and hours if properly prompted. :wink:

2 Likes

I found an interesting gist kind of doing all the explanation needed.

It also showcases what’s so interesting about ECS: There is no one-implementation that rules them all.
However, this one in particular seems to reflect the type of ECS you described. I assume Zay-ES works in this concept, too?

more like “ECS by Adam Martin” one, but little extended.

I think that is the one I linked to, or did you mean a different one?
In any case, that gist above helps a lot. I wasn’t even aware that ECS is not one concept, but many - with some generally more favored than others due to performance impact.

well, concept is the same, like your link mention:

  • entity = index (aka ID), not a class, no logic, no data
  • component = class or struct: no logic, only data
  • componentSystem = logic + array of components (data) of the same type

this is all needed to know. Tho i like TrackLists solution for “array of components”, since you can also have multiple tracklists too.

I also have custom ECS library, where i cache more things than usual ECS would do.

What i mean is that based on this 3 conditions, you can create a lot of different ECS solutions.

ZAY-ES is default one used in JME.

1 Like

Ahh, I see what you mean now.
What I lacked was the knowledge that ECS is more or less a broad term for something that can be implemented in vastly different ways.
Can I implicitly derive from your reply that setting up your own ECS is also not too uncommon?
'Cause I’m one of those devs that likes to implement as much as possible on my own - and doing an ECS Adam Martin style with a ‘System’ ruling over a simplistic array of components seems kind of nice.

Sure you can, but its “re-inventing wheel”, unless you need specific implementation for game needs, or its for learning purpose.

Now imagine Network too, where you will also need write Network Sync for ECS, while ZAY-ES already got some.

1 Like

Yeah, I can see where a DIY solution would fall short quickly, unless considerable time was invested.
In the context of jME, how would one go about implementing a camera in a project with ECS using Zay-ES?
As of now, I have an AppState which sets the camera up and also attaches an InputListener to InputManager for controls (and limits thereof). Would a camera be implemented using ECS at all?

idk for ZAY-ES, but generally i would do:

CameraSystem
CameraComponent

and one entity(or more, but no need more cameras in reality) that have both CameraComponent and LocationComponent(and maybe other components that need for functioning)

CameraSystem to require(track) both components for its Entity track list. (so track list to contain only entites that have both of this components)

When CameraComponent change or LocationComponent change, CameraSystem/LocationSystem would track change and do logic for camera to match components data.(move camera/rotate as need, apply scale or frustrum / etc etc - simple example, check if Camera is near something).

But in many games, i belive camera dont even need to be in ECS.(as entity)

1 Like

Makes sense, that seems the most obvious.
I just have a hard time thinking about how the CameraSystem would handle inputs. Would it just do exactly what my AppState is doing right now? That is, attaching an InputListener to the InputManager?
I guess my mind just has a barrier between ECS and jME-specific functions. But I guess the wiki would help in case I went with Zay-ES.

Well, a lot of solutions. In my case i have Systems also to got “onInit,onRemove,onUpdate(tpf)” like AppState so i have everything in one place.

But most proper i belive would be system to make AppState added for certain camera(well, you control one camera), when it detects(camera entity is added) camera that need to be controlled and remove this AppState when there is no Camera to control.

This also all depends if you need “LocationComponent” to be one that determine location, then i belive System should just have listeners to change this component Data and pass into Camera via System.

1 Like

Hmm yeah, that seems to make the most sense.
Alright, I’m at least fully convinced that my project should utilize ECS and will look into Zay-ES via the wiki. It just doesn’t seem worth the time building a different solution from within plain jME without ECS.

1 Like

It’s not really though. It’s a very specific thing and then a lot of pretenders who say “Mine is an ECS because I have things called entities and things called components.” Even JME devs used to claim (11+ years ago) that JME was an ECS because of Controls… thinking that controls were somehow components. (They aren’t.)

That’s right, usually camera is not a game object.

Input moves the game object (which is an entity)… camera may or may not be linked to that spatial… or could be an overhead camera… or could be stationary and the world moves underneath it.

The useful thing about using a prebuilt ECS library is that you get to scratch your head about why something is the way it is and try to realize that “why” is very critical to what makes an ECS. If you stay pure like Zay-ES you get a lot of cool things for free like lock-free multithreading, networking, persistence, etc… Technically, Zay-ES could even wrap a massively distributed architecture where systems live on different servers (but nothing implements that today).

Entity Systems in a Nutshell
Entity systems ORIGINALLY started in the realm of pure data-oriented design. It was an answer to console platforms that had a streaming memory design where memory locality was literally the most important thing.

In this kind of architecture, you have to group data together where you need it instead of spread out all over memory in different objects. Let’s take a simple example for comparison.

Regular game object with simple physics pseudo-code:

class GameObject {
    vec3 pos;
    vec3 velocity;

    void integrate( timeStep ) {
        pos = pos + velocity * timeStep;
    }
}

Then in classic design, something would iterate over all of the GameObjects and call intetgrate() jumping all over the heap.

In a purely data-oriented you would flatten all of the game objects into one big buffer:
o1.pos.x, o1.pos.y, o1.pos.z, o1.vel.x, o1.vel.y, o1.vel.z
o2.pos.x, o2.pos.y, o2.pos.z, o2.vel.x, o2.vel.y, o2.vel.z

o[n].pos.x, o[n].pos.y, o[n].pos.z, o[n].vel.x, o[n].vel.y, o[n].vel.z

Then a physics routine would stream that RAM, perform the pos + velocity * timeStep operation (maybe even with SIMD type of instructions) and then write the result to some output buffer. That output buffer might be the input to some other code and so on.

An illustrative example is to over-decompose physics into an acceleration and a velocity pass. Let’s imagine a case where some objects have acceleration and some don’t.

One step would flatten all of the acceleration-having game objects into one big buffer with acceleration and velocity elements, do one big pass to calculate the new velocity, then output that information to a new velocity buffer.

The next step would flatten all of the game objects into a pos and (possibly updated) velocity buffer and loop over that like my original example.

So those are the roots of these ideas and where a lot of the “rules” come from… because it turns out that if you decompose your logic this way: data together, logic separate, then you can get some design benefits even if you aren’t constrained by streaming.

Some benefits of following the “rules”:

  • logic code can be simple and dedicated to its task (many times even reusable across games)
  • logic code is inherently parallel. In my above example, it’s possible to run the velocity update and position update simultaneously. If your list of “game objects” is super super large, you could even start the next update pass before the previous one had finished and nothing bad happens.
  • the assembly process leads to a kind of “duck typing” where the behavior of objects is not defined by a rigid class hierarchy but by a combination of the data they contain. If it has position and velocity then the physics integrator is interested. If it has position and a map icon then the map view is interested. If it has position and a model then the regular scene is interested and so on.
  • adding new systems rarely affects the operation of other systems. For example, if I decide I want some objects to decay after a certain amount of time then I can add decay information to the object and a decay system will delete them when they expire. None of the other systems need to care about that. Objects will just disappear underneath them at some point… but since they conceptually get a new set of data every time then they don’t care.

In an ECS, the components are just the pieces of data… typed in such a way that you can easily tell the difference between Position(x,y,z), Velocity(x,y,z), and Acceleration(x,y,z). An entity is just a collection of those components. The systems generally don’t deal with full entities… they deal with the set of components that they are interested in, and then only the entities that actually have those components.

Sometimes as a thought experiment, it can be beneficial to think of an entity system kind of like an SQL database and the logic operates on result sets. (This is where it clicked for me 11 years ago.) The physics system would perform a query for all of the things it wants: position, rotation, linear velocity, linear acceleration. The result set would contain everything that had those parts and the system would loop over that result set and integrate it, then write the new values back out to the database. The data elements in the result set do not change while this is happening… when it’s done, on the next update pass, the system makes the query again and starts over with fresh data.

Obviously that’s not efficient (from a certain perspective) but is a useful thought experiment. (And maybe it is the most efficient if you have billions of objects and don’t care about timely updates but I digress.)

A lot of entity systems will invert this control for you. You register your “system” as some formal thing (probably even implement a System interface), you tell the library what components that system cares about, then the library calls you once per frame with the latest data. It’s very rigid.

Zay-ES takes a slightly different approach by hiding the query+assemble under an EntitySet. This can kind of be thought of as a persistent query that can be repeated when you want. (Like if the SQL ResultSet let you ‘refresh’ it by rerunning the query that created it.)

EntitySet physicsObjects = entityData.findEntities(Position.class, Acceleration.class, Velocity.class);
...
entityObjects.applyChanges(); // reissue the "query", ie: get the latest data
for( Entity e : physicObjects ) {
    // do the integration, set the new components back to the entity
}

In practice, Zay-ES is not really reissuing a query but doing clever caching under the covers…and as long the system code follows “the rules”, changes are applied automatically. (And if nothing has changed then applyChanges() is a no-op.)

But because each EntitySet is its own view, you could technically have a separate thread handle each different system/EntitySet and nothing bad happens.

It’s trivially possible to implement the more rigid “implement a system” style design on top of Zay-ES but it’s also really nice that sometimes you can just grab a set of entities, look at them, and throw them away. (Zay-ES also has the added benefit of being able to tell you what changed about the EntitySet membership.) If Zay-ES had chosen the rigid design then it would not be so easy to go the other way.

I’m going to stop here so all of that can sink in and foster new questions.

I also highly recommend looking at the Asteroid Panic example (https://github.com/jMonkeyEngine-Contributions/zay-es/tree/master/examples/AsteroidPanic) I already posted with the above description in mind. Hopefully things will start to make more sense. (Arguably: Asteroids is the simplest game possible to make and so the hope was that implementing it with an ECS would be as illustrative as possible. It might feel like it doesn’t improve over a pure-game object design but that’s a) beside the point of this example, and b) ignores how much easier it would be to add additional features.)

9 Likes

(crickets)
Let me know if you have any questions about the above. I believe that’s the most solid and encapsulated ECS background that I’ve ever presented in one place.

…to the point that I’d like to make it the basis of an article somewhere but not if it just muddies the water.

2 Likes