33
\$\begingroup\$

I have designed an entity system for an FPS. It basically works like this:

We have a "world"-object, called GameWorld. This holds an array of GameObject, as well as an array of ComponentManager.

GameObject holds an array of Component. It also provides an event mechanism which is really simple. Components themselves may send an event to the entity, which is broadcasted to all the components.

Component is basically something that gives a GameObject certain properties, and since GameObject is actually just a container of them, everything that has to do with a game object happens in the Components. Examples include ViewComponent, PhysicsComponent and LogicComponent. If communication between them is needed, that can be done through the use of events.

ComponentManager just an interface just like Component, and for each Component class, there should generally be one ComponentManager class. These component managers are responsible for creating the components and initializing them with properties read from something like an XML-file.

ComponentManager also takes care of mass updates of components, like the PhysicsComponent where I will be using an external library (which does everything in the world at once).

For configurability, I will be using a factory for the entities which will read either an XML-file or a script, create the components specified in the file (which also adds a reference to it in the right component manager for mass updates), and then inject them into a GameObject object.

Now comes my problem: I am going to try to use this for multiplayer games. I have no idea how to approach this.

Firstly: What entities should the clients have from the beginning? I should start with explaining how a single-player engine would determine what entities to create.

In the level editor you can create "brushes" and "entities". Brushes are for things like walls, floors and ceilings, basically simple shapes. Entities are the GameObject I have told you about. When creating entities in the level editor, you can specify properties for each of it's components. These properties are passed directly to something like a constructor in the entity's script.

When you save the level for the engine to load, it is decomposed into a list of entities and their associated properties. The brushes are converted into a "worldspawn" entity.

When you load that level, it just instanciates all the entities. Sounds simple, eh?

Now, for networking the entities I run into numerous problems. First, what entities should exist on the client from the beginning? Assuming that both the server and client has the level file, the client could might as well instanciate all the entities in the level, even if they are there just for the purposes of game rules on the server.

Another possibility is that the client instanciates an entity as soon as the server sends information about it, and that means that the client will only have entities that it needs.

Another issue is how to send the information. I think the server could use delta-compression, meaning that it only sends new information when something changes, rather than sending a snapshot to the client at every frame. Though that means that the server must keep track of what each client knows at the moment.

And finally, how should networking be injected into the engine? I'm thinking a component, NetworkComponent, which is injected into every entity that is supposed to be networked. But how should the network component know what variables to network, and how to access those, and finally how the corresponding network component on the client should know how to change the networked variables?

I'm having huge trouble approaching this. I would really appreciate if you helped me on the way. I'm open to tips on how to improve the component system design too, so don't be afraid of suggesting that.

\$\endgroup\$

2 Answers 2

14
\$\begingroup\$

This is a beast of a question with lots of details, +1 there. Definitely enough to help people who stumble upon it.

I just mostly wanted to add my 2 cents about not sending physics data!! I honestly can't stress this enough. Even if you have it so far optimized that you can practically send 40 spheres that bounce around with micro collision and it could go full speed in a shaking room that doesn't even decrease frame rate. I'm referring to performing your "delta-compression/encoding" also known as data-differencing that you discussed.

Dead Reckoning VS Data Differencing: They are different enough and really don't occupy the same methods, meaning that you could implement both to increase optimization even further! Note: I have not used them both together, but have worked with both.

Delta encoding or data-differencing: The server carries data on what clients know, and sends only the differences between old data and what should be changed to the client. e.g. pseudo-> in one example you might send the data "315 435 222 3546 33" when the data is already "310 435 210 4000 40" Some are only slightly changed, and one isn't changed at all! Rather than that, you would send (in delta) "5 0 12 -454 -7" which is considerably shorter.

Better examples might be something that changes a lot farther than that for example lets say I have a linked list with 45 linked objects in it right now. I want to kill 30 of them, so I do that, then send to everyone what the new packet data is, which would slow down the server if it wasn't already built for doing things like this, and it happened because it was trying to correct itself for example. In delta encoding, you would simply put (pseudo) "list.kill 30 at 5" and it would remove 30 objects from the list after the 5th one then authenticate the data, but on each client rather than the server.

Pros: (Can only think of one of each right now)

  1. Speed: Obviously in my last example I described. It would be a lot larger of a difference than the previous example. In general I can't honestly say from experience which of those would be more common, as I work a lot more with dead reckoning

Cons:

  1. If you're updating your system and want to add some more data that should be edited through the delta, you'll have to create new functions to change that data! (e.g. as earlier "list.kill 30 at 5" Oh no, I need an undo method added to the client! "list.kill undo")

Dead reckoning: Simply stated, here's an analogy. I'm writing a map for someone on how to get to a location, and I'm only including the points of where to go in general, because it's good enough (stop at building, turn left). Someone else's map includes the street names and also how many degrees to turn left, is that necessary at all? (No...)

Dead reckoning is where each client has an algorithm which is constant per client. Data is pretty much just altered by saying which data needs altered, and how to do it. The client changes the data on its own. An example is that if I have a character that isn't my player, but is being moved by another person playing with me, I shouldn't have to update the data every frame because a lot of the data is consistent!

Lets say I have my character moving in some direction, many servers will send data to the clients that says (almost per frame) where the player is, and that it is moving(for animation reasons). That's so much unnecessary data! Why the hell do I need to update every single frame, where the unit is and what direction it's facing AND that it's moving? Simply put: I don't. You update the clients only when the direction changes, when the verb changes (isMoving=true?) and what the object is! Then each client will move the object accordingly.

Answers

To be quite frank, read James's post and read what I said about the data. Yes you should most definitely use delta-encoding, but think about also using dead reckoning.

Personally I would instantiate the data on the client, when it receives information about it from the server (something you suggested).

Only objects that can change should ever be noted as being editable in the first place right? I like your idea of including that an object should have network data, through your component and entity system! It's clever, and should work just fine. But you should never give brushes (or any data that's absolutely consistent) any networking methods at all. They don't need it, since it's something that can't even change (client to client that is).

If it's something like a door, I'd give it networking data but only a boolean on whether it's open or not, then obviously what kind of object it is. The client should know how to alter it, e.g. it's open, close it, each client receives that they should all close it, so you change the boolean data, then animate the door to being shut.

As for how it should know what variables to network, I might have a component that is truly a SUB-object, and give it components that you would like to network. Another idea is to not just have AddComponent("whatever") but also AddNetComponent("and what have you") just because it sounds more organized personally.

\$\endgroup\$
0
3
\$\begingroup\$

Was going to write a comment but decided this might be enough information for an answer.

First, +1 for such a nicely written question with tons of details to judge the answer by.

For the data loading I would have the client load the world from the world file. If your entities have Ids in them that come from the data file then I would also load them by default so your networking system can just refer to them to know which objects it is talking about. Everyone loading the same initial data should mean they all have the same Ids for those objects.

Secondly, do not make a NetworkComponent component as this would do nothing but replicate data in other existing components (physics, animation and the like are some common things to send across). To use your own naming you might want to make a NetworkComponentManager. This would be slightly off from the other Component to ComponentManager relationship you have but this could be instantiated when you start a networked game and have any type of components that have a networking aspect to them give their data to the manager so it can package it up and send it off. This is where your Save/Load functionality could be put to use if you have some sort of a serialization/deserialization mechanism that you could also use to package up data for, as mentioned, giving it to the NetworkComponentManager to package up with the rest of its information and send off to the other player.

Given your question and level of information I do not think I need to go into much more detail, but if anything is not clear please post a comment and I will update the answer to address this.

Hope this helps.

\$\endgroup\$
1
  • \$\begingroup\$ So, what you're saying is that components that should be networked should implement some kind of interface like this?: void SetNetworkedVariable(string name, NetworkedVariable value); NetworkedVariable GetNetworkedVariable(string name); Where NetworkedVariable is used for the purposes of interpolating and other network stuff. I don't know how to identify which components that implement this though. I could use runtime type identification, but that seems ugly to me. \$\endgroup\$
    – Carter
    Dec 14, 2011 at 16:59

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .