Today I started the "no-going-back" multiplayer conversion for AstroMiner. While I do have many backups and I could theoretically go back to day one, I am considering this the point of no return.
As I mentioned before, a lot of code has to be changed to allow multiplayer support. I could hack it in and make it so it's not nearly as much effort, but that runs the risk of future hassles whenever I add new features. There would be redundant code running on both client and server that wouldn't need to be run. How do I know which results to trust? Server or client? What if my client disagrees with someone else's? These are all questions that I asked myself when I decided to make this conversion. If you want more detail, read my blog post from a few days ago that describes my model for multiplayer.
So today I started removing code from the client. When the game launches, the server is also launched silently in the background. The client no longer saves or loads maps. That's all handled by the server now. When you start a new map, you actually send a command to the server telling it "load this map" or "start a new map with this seed". The server then does its business and sends another packet back telling you it's done. At that point, the client says "give me all the updates around my position". This was the tricky part.
Gathering the updates wasn't hard. Building a container class for them to be transported wasn't hard. What was hard is figuring out a solid way to transport them without taking so much space. I toyed around with several serializers. XMLSerializer was slow and took up a lot of space. BinaryFormatter was fast, but it can't handle Vector2's. I tried some custom libraries, such as the Sharp Serializer. This is an awesome little library and would've worked, except it was giving me issues with Vector2's as well. Vector2 is an XNA format and I can't modify it, so I needed something that would let me define my own custom serialization types. Google's Protobuf came to the rescue. More specifically, Marc Gravell's Mono Implementation. This not only lets me define custom types, it is extremely fast when it serializes and extremely compact. For the sake of internet searches, I'm going to post my code to serialize an XNA Vector2:
First, you want to declare a RuntimeTypeModel:
Then in your initializer, you need to instantiate this model with all of your custom classes. Mine looks like this:
By the way, I did all the coloring by hand. You're welcome.
Ok so to explain that, I'm creating a model so Protobuf knows how to serialize my object. For Vector2's, I'm telling it I only need the .X and .Y values. For all my custom classes (chunkUpdate, change, block), I'm telling it to figure it out on its own. It's also reading XNA's Rectangle on its own. I'm willing to bet it can read Vector2 on its own, but I haven't tested it.
Anyway, once that's all fancy and done, I simple serialize like so:
fullPacketStream is a MemoryStream and cu is my chunkUpdate object that I'm serializing. I turn then fullPacketStream into a byte array and send it off on its journey to the client. When the packet is received, I deserialize like so:
I now have an intact chunkUpdate class that contains all of the updates for a given chunk of data.
I spent most of the evening learning about protobuf and getting this to work. I think that by tomorrow night, I should be at the point where I can create a new map or load an existing one. The map will be able to save itself to desk when the server is done with it and the player will be able to travel all throughout the asteroid field and have data being streamed to them on demand. (I just jumped from first person to third)
Anyway, no screenshots to show because it's all just code :[ I will hopefully have something tomorrow though when I get the full streaming set up.
Until then ~
As I mentioned before, a lot of code has to be changed to allow multiplayer support. I could hack it in and make it so it's not nearly as much effort, but that runs the risk of future hassles whenever I add new features. There would be redundant code running on both client and server that wouldn't need to be run. How do I know which results to trust? Server or client? What if my client disagrees with someone else's? These are all questions that I asked myself when I decided to make this conversion. If you want more detail, read my blog post from a few days ago that describes my model for multiplayer.
So today I started removing code from the client. When the game launches, the server is also launched silently in the background. The client no longer saves or loads maps. That's all handled by the server now. When you start a new map, you actually send a command to the server telling it "load this map" or "start a new map with this seed". The server then does its business and sends another packet back telling you it's done. At that point, the client says "give me all the updates around my position". This was the tricky part.
Gathering the updates wasn't hard. Building a container class for them to be transported wasn't hard. What was hard is figuring out a solid way to transport them without taking so much space. I toyed around with several serializers. XMLSerializer was slow and took up a lot of space. BinaryFormatter was fast, but it can't handle Vector2's. I tried some custom libraries, such as the Sharp Serializer. This is an awesome little library and would've worked, except it was giving me issues with Vector2's as well. Vector2 is an XNA format and I can't modify it, so I needed something that would let me define my own custom serialization types. Google's Protobuf came to the rescue. More specifically, Marc Gravell's Mono Implementation. This not only lets me define custom types, it is extremely fast when it serializes and extremely compact. For the sake of internet searches, I'm going to post my code to serialize an XNA Vector2:
First, you want to declare a RuntimeTypeModel:
private static RuntimeTypeModel serialize;
Then in your initializer, you need to instantiate this model with all of your custom classes. Mine looks like this:
public netManager()
{
serialize = RuntimeTypeModel.Create();
serialize.Add(typeof(Vector2), false).Add("X", "Y");
serialize.Add(typeof(chunkUpdate), true);
serialize.Add(typeof(change), true);
serialize.Add(typeof(block), true);
serialize.Add(typeof(Rectangle), true);
By the way, I did all the coloring by hand. You're welcome.
Ok so to explain that, I'm creating a model so Protobuf knows how to serialize my object. For Vector2's, I'm telling it I only need the .X and .Y values. For all my custom classes (chunkUpdate, change, block), I'm telling it to figure it out on its own. It's also reading XNA's Rectangle on its own. I'm willing to bet it can read Vector2 on its own, but I haven't tested it.
Anyway, once that's all fancy and done, I simple serialize like so:
serialize.Serialize(fullPacketStream, cu);
fullPacketStream is a MemoryStream and cu is my chunkUpdate object that I'm serializing. I turn then fullPacketStream into a byte array and send it off on its journey to the client. When the packet is received, I deserialize like so:
serialize.Deserialize(chunkUpdateMemoryStream, chunkUpdateInput,
typeof
(chunkUpdate));
I now have an intact chunkUpdate class that contains all of the updates for a given chunk of data.
I spent most of the evening learning about protobuf and getting this to work. I think that by tomorrow night, I should be at the point where I can create a new map or load an existing one. The map will be able to save itself to desk when the server is done with it and the player will be able to travel all throughout the asteroid field and have data being streamed to them on demand. (I just jumped from first person to third)
Anyway, no screenshots to show because it's all just code :[ I will hopefully have something tomorrow though when I get the full streaming set up.
Until then ~
1 comment:
thank you for this one sir.
Post a Comment