Component-based entities, looking for feedback (playable Asteroids clone!)

Started by
9 comments, last by Olof Larsson 12 years, 10 months ago
I love using &#106avascript for prototyping. Its dynamic nature and relative simplicity helps me solve problems without worrying about memory management and such. I've been reading about component-based entities, and decided to try implementing a component-based entity system in a small game before attempting to develop one in a larger project. When trying to find hidden walls in a space that you don't understand well, it's often effective to just blindly run into them head first! Naturally, it's better to do this in a small, safe environment than a large one with much at stake. I chose to make a simple Asteroids clone. Please feel free to play it, download it, poke its code with a stick, etc. Edit: Oh, and BTW, the game depends on the canvas and audio tags. It'll run best in recent versions of Safari, Firefox, and Opera. There's a small audio issue in Chrome, and I don't think it'll work at all in IE. http://donaldhays.com/projects/spaceRockShooter/ I have a few thoughts and things I would like feedback on. The details of my approach. There is a core "class" called Entity. It is little more than a set of components, as well as some metadata like ids and types. I have some logic for dynamic retrieval of entities based on type and id. A component is an object encapsulating some data and logic. It has a reference to its associated entity. There aren't really any surprises here. The game has an event system. Anything can register to listen to events. Registration involves passing an entity to listen to, the name of the event, and the function to invoke when the event occurs. Anything can trigger an event. Triggering involves passing an entity to trigger the event on, the name of the event, and any miscellaneous data associated with the event. What is the virtue of a pure-aggregation approach? A commonly-cited article recommends a pure-aggregation model, where there is no explicit Entity type, and entities only exist in the sense that a set of different components share the same entity id. The article seemed light on reasons for doing this over the approach I took (my approach including an explicit Entity type). I'm not intuitively arriving at any real advantages to a pure-aggregation approach, so I'm just wondering if anybody could enlighten me. Sudden disappearing entity/component problem. When a bullet hits an asteroid, the asteroid reacts to the collision event by destroying itself and the bullet. This happens in the middle of a loop in the physics engine that iterates over all physics components. This results in a scenario where a list (the list of physics components) changes while it is being iterated over (the asteroid deletes itself and a bullet). This is an opportunity for error, and I indeed had some issues until I added some extra case-specific code. I didn't like the solution, though, and I'm wondering if anybody has a generic approach to these kinds of scenarios that doesn't involve sprinkling sanity checks everywhere. I think component-based entities were, on net, a pretty big win for me. This project only has about 1,000 lines of code, so it isn't very big, but it had a real vibe of coming together much more nicely than other projects of similar scope have. Methods are small and straightforward, modules aren't coupled all that tightly, things don't really feel like they're doing more jobs than they should, etc. I felt like I was able to just keep moving forward with feature adds or mutations to existing features without breaking things. It was nice.
Advertisement
Firstly, that's a nicely structured game with very clean code.

In reply to your question "Sudden disappearing entity/component problem", what you have there is a synchronization issue (edit, actually it isn't, since it happens in a single thread, but the following still applies). There are a couple of ways you could fix it.

1. Make a copy of the physicsComponents array in physicsTick function. That way it won't change while you're using it, only on the next iteration.

2. Instead of remove entities directly from it, add all the entities to be removed to a separate list, and process that list after you've iterated through them all.

One odd thing I noticed was your worldWrapper component. Shouldn't this function be handled in the physics component? So rather than doing:
        position.x += velocityX;        position.y += velocityY;

do this instead:
        position.x = (position.x + velocityX) % width;        position.y = (position.y + velocityY) % height;


Another suggestion would be to split out the level logic into it's own entity/component, and have the gameRules component listen for a levelCompleted or playerDied events from it.

Regards
elFarto

[Edited by - elFarto on May 26, 2010 3:10:14 AM]
Quote:Original post by DonaldHays
What is the virtue of a pure-aggregation approach?

I haven't found it. I also have an explicit Entity class. It stores positional data, a list of components, and some metadata. I don't see any reason to move this data into components, since almost every other component needs it. It doesn't make sense to eliminate Entity just for the sake of a "pure" approach.

Quote:Sudden disappearing entity/component problem. When a bullet hits an asteroid, the asteroid reacts to the collision event by destroying itself and the bullet. This happens in the middle of a loop in the physics engine that iterates over all physics components. This results in a scenario where a list (the list of physics components) changes while it is being iterated over (the asteroid deletes itself and a bullet). This is an opportunity for error, and I indeed had some issues until I added some extra case-specific code. I didn't like the solution, though, and I'm wondering if anybody has a generic approach to these kinds of scenarios that doesn't involve sprinkling sanity checks everywhere.

There's a bunch of ways to handle this. The simplest is to create a list of iterators referencing to-be-destroyed objects, and then erase each one in turn after the update.

Another way is to send out an EntityDestroyed message to every subsystem (including the one responsible for sending the message), and only remove components in response to this message. This way, the actual removal of the Entity from the list occurs whenever you do your message processing, not during the physics update. This will only work, of course, if your message processing doesn't occur instantaneously.

Quote:I think component-based entities were, on net, a pretty big win for me. This project only has about 1,000 lines of code, so it isn't very big, but it had a real vibe of coming together much more nicely than other projects of similar scope have. Methods are small and straightforward, modules aren't coupled all that tightly, things don't really feel like they're doing more jobs than they should, etc. I felt like I was able to just keep moving forward with feature adds or mutations to existing features without breaking things. It was nice.

I've found the same. The system is easy to use and infinitely extensible. It's a definite winner for me.
Quote:Original post by elFarto
Firstly, that's a nicely structured game with very clean code.


Thank you kindly!

Quote:Original post by elFarto
In reply to your question "Sudden disappearing entity/component problem", what you have there is a synchronization issue (edit, actually it isn't, since it happens in a single thread, but the following still applies).


A thought that went through my head when I was addressing the issue: "this would suck even more in a multithreaded world."

Quote:Original post by elFarto
1. Make a copy of the physicsComponents array in physicsTick function. That way it won't change while you're using it, only on the next iteration.

2. Instead of remove entities directly from it, add all the entities to be removed to a separate list, and process that list after you've iterated through them all.

Quote:Original post by Ariste
There's a bunch of ways to handle this. The simplest is to create a list of iterators referencing to-be-destroyed objects, and then erase each one in turn after the update.

Another way is to send out an EntityDestroyed message to every subsystem (including the one responsible for sending the message), and only remove components in response to this message. This way, the actual removal of the Entity from the list occurs whenever you do your message processing, not during the physics update. This will only work, of course, if your message processing doesn't occur instantaneously.


I tried a deferred deletion approach at first and ran into a somewhat different issue. If a bullet intersects multiple asteroids when it runs a physics step, it would destroy all intersecting asteroids before going away. While the player might be tickled happy with that, it's not what I wanted. To solve that issue the bullet would need to mark itself as spent in some way, and subsequent physics tests would need to check against that.

I like the deferred messaging, though. It reminds me a little of Cocoa's Event Loop architecture. I could queue up events, execute them in turn, and take any events out of the queue if their associated entities are destroyed. Seems a promising direction.

Quote:Original post by elFarto
One odd thing I noticed was your worldWrapper component. Shouldn't this function be handled in the physics component? So rather than doing:
*** Source Snippet Removed ***
do this instead:
*** Source Snippet Removed ***


For whatever reason, I felt compelled to have the player's bullets not wrap around the world. To accomplish the same behavior in the physics component would require exposing a worldWrap flag in the physics component, which would not be unreasonable, but I wanted to try components in many many places :D. I seem to recall hearing of physics engines that get fussy if you let something other than the physics engine itself move an object around, so that probably gives more weight to allowing world wrapping only inside the physics component.

Quote:Original post by elFarto
Another suggestion would be to split out the level logic into it's own entity/component, and have the gameRules component listen for a levelCompleted or playerDied events from it.


That's reasonable, particularly on the levelCompleted event. As-is the game rules determine when a level is complete based off the number of remaining asteroids, which is a piece of logic that could also appropriately be determined by a level entity.

Quote:Original post by Ariste
I haven't found it. I also have an explicit Entity class. It stores positional data, a list of components, and some metadata. I don't see any reason to move this data into components, since almost every other component needs it. It doesn't make sense to eliminate Entity just for the sake of a "pure" approach.


That's what it seems like to me, it just appears to be a popular thing to do, and I'm wondering if there's a particular advantage to it versus just being trendy.
If you can do away with Entities entirely, then it seems like a very clean approach. Just update each subsystem and each one updates the components it knows about. Why have it iterate over entities which typically have a one-to-one relationship with the components of interest? Couldn't all this per-entity data just become another type of component?
Did not get a chance to look through code yet but I wanted to leave you a quick comment: I used it on Chrome and I didn't notice any bugs or audio bugs. I'm using the latest beta version of Chrome so I don't know if that is why I didn't notice them though.
Quote:Original post by Kylotan
If you can do away with Entities entirely, then it seems like a very clean approach. Just update each subsystem and each one updates the components it knows about. Why have it iterate over entities which typically have a one-to-one relationship with the components of interest? Couldn't all this per-entity data just become another type of component?

The thing is, some properties need to be visible to nearly every component. What's the point of sticking them in their own component? What do you gain?

Note that Entity doesn't even need to be updated. In my implementation, it's not. It's essentially a container for positional and rotational data with some metadata attached. Entities don't act; they're acted upon by their components. Moving Entity's data into its own subsystem would only complicate this process.

I also find it convenient to use Entity as an interface for component retrieval. If one component needs access to another component, it queries its Entity. This makes sense conceptually, and it's really easy to implement. You don't need to pass around pointers to every subsystem or some other such mechanism.
Quote:Original post by Ariste
Quote:Original post by Kylotan
If you can do away with Entities entirely, then it seems like a very clean approach. Just update each subsystem and each one updates the components it knows about. Why have it iterate over entities which typically have a one-to-one relationship with the components of interest? Couldn't all this per-entity data just become another type of component?

The thing is, some properties need to be visible to nearly every component. What's the point of sticking them in their own component? What do you gain?

Note that Entity doesn't even need to be updated. In my implementation, it's not. It's essentially a container for positional and rotational data with some metadata attached. Entities don't act; they're acted upon by their components. Moving Entity's data into its own subsystem would only complicate this process.

I also find it convenient to use Entity as an interface for component retrieval. If one component needs access to another component, it queries its Entity. This makes sense conceptually, and it's really easy to implement. You don't need to pass around pointers to every subsystem or some other such mechanism.


In your engine, you've made an assumption that every entity has a position.

Consider for a moment that you have an entity that simply plays music. It's not environmental - it just plays music at an adjustable volume. It has no spatial location, so why suggest that it does by including position in the Entity class? If you have an Entity class, I believe it should be as light weight as possible, presenting the bare minimum required for finding components and identifying them.

In thinking about how I would go about implementing a component-based entity system in plain-old-c (which has been, shall we say, an interesting exercise), I've been finding that there would likely be little difference for me between a system with a dedicated Entity type and a pure-aggregation system. While in my current &#106avascript code all components hold references to their associated entity, in a pure-aggregation approach they would simply have an entity id.

For a component to get another component on the same entity, the interface in plain-old-c would be nearly the same for either system. It would involve calling a getComponentForEntity function, passing either the pointer to the entity in the entity-type system, or the entity id in the pure-aggregation system. The most significant difference would be in performance. Holding a reference to the entity directly grants o(1) access to the entity's collection of components. Needing to lookup the entity given its id involves a likely hashtable search. Both approaches would obviously then need a search of the component collection. Now, would that performance difference actually be problematic? Probably not in the kinds of games I wish to make, no.

I think, at this point, the biggest thing tripping me up is just the weird vibe I get from the pure-aggregation approach. To not have any actual objects that exist that I can point to and say, "that right thar is the entity!" is weird. It's not like the vibe I got when I learned how absolutely everything is an object in Smalltalk (even the classes!), which was like an eye-opening enlightening experience.

My plan has been to try writing the asteroids game several times. I think there's a decent chance that only one rewrite will suffice for my learning purposes. The rewrite was planned from the beginning to try to mimic a pure-c approach (structs and functions, not objects), but now I think I'll also try the pure-aggregation approach, just to see how it goes.

Also, just to comment on one other thing, the entities in my asteroids code do not have position or orientation data, for precisely the reason pointed to by Sphet: not all entities have position or orientation. In particular, my GameRules entity is just such an entity. My entities have a collection of components, a uid used by the system for bookkeeping purposes, a user-settable string id that can be used to get the entity from a global context (getEntityById("gameRules"), getEntityById("player")), and a user-settable type string ("asteroid", "playerShot", "playerShip", etc). That seems a reasonable set of information for an entity to have to me.
My approach to entity deletion is slightly different.
First off, all logic is solely handled via Components.
Entity is just a container for Components and has no logic besides updating its chil entities and components recursively.

Hence inside the component.OnUpdate call, you cannot remove the Entity and/or Component due to the iterator being changed.

What i did to solve this is pretty simple.
A Component exposes a Destroy(Entity entity) method and stores the reference in a static list.
The main game loop then scans the list at the end of the frame, removes the entities in question from the hierarchy and calls their virtual OnDestroy method for finalization.

Works like a charm and even works very well if you use multiple entity hierarchies in parallel.

This topic is closed to new replies.

Advertisement