Hume just posted his Lessons Learned from the warmup for Ludum Dare 23 (48 hours to write a game from scratch – starts this weekend!) – and his positive experience using an Entity System.
In his epic comment (sparked by a different Adam – not me, honest), is this gem:
“Using the entity system for the first time was unreal to me. It’s like polymorphic code. I did really weird things on the fly. For example:
– In the health processor, if the enemy was just destroyed, set a flag in the lifecycle component.
– In the lifecycle processor, if the fresh kill flag is set, extract its loot component and put that into a new entity with a small randomized velocity component and a gravity component so that the loot drops; then, remove most of the other components from the entity and add an explosion component.
The “enemy” still has the same entity ID — any other components that are looking for that entity will still find it (e.g. missiles homing in on the wreckage, or score processors looking for slain entities) — but by swapping one set of data with another, its implementation has changed from an enemy to some kind of non-interactive effect object.”
Identity. It’s important.
(Quick sidenote: for all the people asking questions like “but … which variables do I put in Component A as opposed to Component B? How do I manage Events in an Entity System? … etc” – Hume’s approach above is a good concrete example of the first-draft, easy-to-write way of doing things. Copy it.)
Identity in games
This is one of those things that newbie game programmers seem to underestimate, frequently.
And when I say “newbie” I include “experienced, skilled programmers with 10+ years of coding experience – but who haven’t yet shipped a game of their *own*”.
(e.g. I’ve seen a couple of studios that started as Digital Agencies, or as Animation Studios, etc – that then transitioned to writing their own games. This is the kind of thing that they often struggle with. Not for lack of skill or general programming experience, but for lack of the domain-specific experience of game coding)
Examples of Identity in games, off the top of my head – all of these are independent, and interact in complex ways with each other :
- Game-role: e.g. … “enemy”, “powerup”, “start location”
- Code ‘object’ (in OOP terms): e.g. … “the sprite you are drawing at position (4,5) is part of Object X. X is coloured THIS colour”
- Gameplay ‘object’: e.g. … “the sprite at (4,5) represents a Tank. If a Tank sprite ever touches a Glass sprite, we need to play the Broken Glass noise”
- Physics element: e.g. … “5 milliseconds ago, our Physics Engine thought this thing was THERE. Now it’s over HERE. Don’t confuse the Physics Engine! Make sure it ‘knows’ they are the same object – not two separate objects”
- Network “master/clone”: e.g. … in multiplayer, there are N copies of my avatar: one per computer in the game. One of those N is the original – and changes to the original are constantly used to overwrite the clones; changes to clones are LOCAL ONLY and are discarded. Which is original? What do we do with incoming “changes” – which local Code Object do we apply them to? (your Code Object will be different from my Code Object – but they’ll both be the same identical Network Object, save mine is flagged “clone”)
- Proper Noun object: e.g. … “The Player’s Tank” is a very specific tank out of all the Tanks in the game. Many lines of game code don’t care about anything except finding and operating on that specific tank.
- Game-Over representation: e.g. … after the player has killed all the enemies, and they see a Game Over (won/lost/etc) screen, and you want to list all the enemies they killed … how do you do that? The enemies – by definition – no longer exist. They got killed, removed from the screen, removed from memory. You could store just the absolute numbers – but what if you want to draw them, or replay the death animations?
Identity in Entity Systems
ES’s traditionally give you a SINGLE concept of Identity: the Entity (usually implemented as a single Integer). Hmm. That sounds worryingly bad, given what I wrote above. One identity cannot – by definition – encompass multiple, independent, interrelated identities.
But we’re being a bit too literal here. ES’s give you one PRIMARY identity, but they also give you a bunch of SECONDARY identities. So, in practice…
Secondary Identities in an ES
In OOP, the Object is atomic, and the Class is atomic. You cannot “split” an Object, nor a Class, without re-defining it (usually: re-compile).
In ES, the Entity is atomic, and the Component is atomic. But the equivalent of an OOP Object – i.e. “an Entity plus zero or more Components” – is *not* atomic. It can be split.
And from there comes the secondary identities…
A Primary Identity: e.g. “The Player’s Tank” (specific)
A Primary Identity: e.g. “a Gun Component” (generic)
A Secondary Identity: e.g. “The Gun component … of the Player’s Tank Entity” (specific)
Revisiting my ad-hoc list of Game Identities above, I hope it’s clear that you can easily re-write most of those in terms of secondary identity.
And – bonus! – suddenly the relationships between them start to become (a little) clearer and cleaner. Easier for humans to reason about. Easier *for you to debug*. Easier *for you to design new features*.
Global Identity vs. Local Identity
Noticeably, the network-related Identities are still hard to deal with.
On *my* computer, I can’t reference entities on *your* computer. I cannot store: “The Gun component … of YOUR player’s tank”, because your “Player’s Tank” only exists in the memory of your computer – not mine.
There are (trivially) obvious solutions / approaches here, not least: make your Entity integers global. e.g. split the 64bit Integer into 2 32bit Integers: first Integer is the computer that an Entity lives on, the second is the local Entity Integer. Combined, they are a “global Entity ID”.
(I’m grossly over-simplifying there – if you’re interested in this, google for “globally unique identifiers” – the problems and solutions have been around for decades. Don’t re-invent the wheel)
But … at this point, they also offer you the chance to consider your game’s network architecture. Are you peer-to-peer, or client-server?
For instance, P2P architectures practically beg for unique Global entity numbers. But C/S architectures can happily live off non-global. For instance:
- On each client, there are ONLY local Entity numbers
- When the client receives data from the server, it generates new, local, Entities
- …and adds a “ServerGenerated” component to each one, so it’s easy to see that they are “special” in some ways. That component could hold info like “the time in milliseconds that we last received an update on this object” – which is very useful for doing dead-reckoning, to make your remote objects appear to move smoothly on the local screen
- The server *does* partition all entities from all machines. But none of the clients need to know that
Or, to take it further, if your network arch is any good at all for high-paced gaming:
- The server differentiates between:
- The entity that the game-rules are operating on
- The entity that client 1 *believes* is current
- …ditto for client 2, client 3 … etc (each has their own one)
- The entity that the game-rules accept (e.g. if a hacked client has injected false info, the game-rules may override / rewrite some data in the local object)
- The server also tags all the entities for a single in-game object as being “perspectives on the same thing”, so that it can keep them in synch with each other
- The server does Clever Stuff, e.g.:
- Every 2 milliseconds, it looks at the “current entity”, and compares it to the “client’s belief of that entity”. If it finds any differences, it sends a network message to the client, telling it that “you’re wrong, my friend: that entity’s components have changed their data. They are now X, Y and Z”
… or something like that. Again, I’m grossly over-simplifying – if you want to write decent network code, Google is your friend. But the fastest / lowest latency multiplayer code tends to work something like that.
What do you think?
(hint: you can do wonders using Reflection/Introspection on your entity / components. By their nature, they’re easy to write generic code for.
But you WILL need some extra metadata – to the extent that you may wish to ‘upgrade’ your Entity System into a SuperEntity System – something with a bit more expressive power, to handle the concept of multiple simultaneous *different* versions of the same Entity. Ouch)
Yeah, I’m bailing on you here. Too little time to write much right now – and it’s been a *long* time since I’ve implemented this level of network code for an ES. So, I’m going to have to think hard about it, refresh my memory, re-think what I think I knew. Will take some time…
5 replies on “Concepts of “object identity” in game programming…”
Adam, this is my chance to thank you for sharing your expertise with Entity Systems. I am almost precisely the “newbie game programmer” you describe, so I’ve tried to direct people here and to the wiki to get the real goods on the system. But my enthusiasm for the concept does lead to epic comments.
I’ve been considering how to retrofit a more sophisticated implementation of ES into my main project — a multiplayer game with shared physics, already using a “client belief” system to minimize network updates — so I’ll be following your thoughts here very closely. I was sold on the value of GUIDs (even for client-server) long before my Ludum Dare experiment, so I expect the transition to be less painful than otherwise.
I have some vague notions of the need for a versionable identity. In my testing, a simple incremented version or timestamp scheme breaks down; it develops conflicts, the same way source control systems with incremental revisions have conflicts. The best I can manage at the moment is a sort of patch component, which is workable for deltas but not so well for other types of conflicts. When this issue comes up again, my next thought was to look at modern revision control systems like Git to see if there are any ideas that will transfer.
But I’m really looking forward to whenever you have the time to describe SuperEntities. Can’t wait.
To be clear: I don’t have a master plan for super entities – although IIRC Joel McGinnis’s (now at CCP) plans 6 years ago for component management (treating each component as if it were a memory block, and doing mem-management style stuff (virtual, real, etc) on top of that) seemed a good start.
Hi Adam, I too would also like to thank you for sharing so much information about ECS. I have been digging through everything I can find out about it in your blogs, the wiki and the projects you have linked to.
I have been trying to figure out how to apply all of this to the typical game server with multiple player clients.
I think I have a “simple” question that might have a complex answer, but it would help me clear up several areas where I am struggling with the concepts.
Which systems do you put on the server, and which ones are on the client, and then which ones might be on both?
For a specific question, would you put the system that manages entity location and velocity on the server, or would you let the client handle some of that load? It seems that having the server update clients of the location of a fast moving object would cause a lot of data transfer over the network.
This probably isn’t entirely a question about ECS now that I think about it, but still, perhaps you can shed some light for a newbie.
Everything goes on both.
You’re thinking about it wrong. Google info on Quake3 Networking.
Awesome! I took your advice and am now understanding even more, thanks again :)
I found a link to a post by John Carmack and was reading through it, this snippet below helped clear up a lot for me. I think I was working under the “client-as-terminals” idea that he mentions.
“The biggest difference is the addition of client side movement simulation.
I am now allowing the client to guess at the results of the users movement
until the authoritative response from the server comes through. This is a
biiiig architectural change. The client now needs to know about solidity
of objects, friction, gravity, etc. I am sad to see the elegent
client-as-terminal setup go away, but I am practical above idealistic.
The server is still the final word, so the client is allways repredicting
it’s movement based off of the last known good message from the server.”