Unity Project
Okay, so let us start on where we plan on going forward with our Unity Senior Project. This week we are creating a Project Plan (PP) and finishing our Technical Design Document (TDD).
As far as the PP goes, we have a small schedule of work listed out in our TDD draft. With a group of four, we are planning on dividing the work amongst ourselves based on our talents. We will have two people doing the scripting/programming, a sound artist, and a 3D modeler/graphic designer. Hopefully this will lead to efficient workflow and keep us from stepping on each others' toes.
The TDD draft did excellent in our class and really does not need much work for the rest of the course. A lot of the sections (graphics engine, physics engine, messaging system, etc.) relates directly with the Unity game engine. So as far as that is concerned, check out Unity's website to see the technologies they utilize in their game engine ( https://unity3d.com/ ).
Right, so we have covered and overview of a small design aspect of the game. Let us move over to some of the technical parts.
Before I left for vacation I had put up a simple demo video of some raw gameplay based on our networked games. So far we are able to connect to a server running SmartFox, login as a guest, chat, and play a simple game. How is this all accomplished with Unity and SmartFox? Well, SmartFox has a lot of excellent examples on thier website; however, they are examples on what to type in order to get something working. Here, I have a fully applicable, designed solution, to how this is all working.
There are two core design patterns at play with our implementation of the server interaction client side. So for now, I will be discussing the Unity (client) side of things in regards to how we designed our game. I urge you to download/checkout our source code to be able to follow along. ( https://github.com/hollsteinm/GSPSeniorProject )
Alright, so let us get started.
The two core design patterns we utilized our game so far are the Observer pattern as a messaging system and the Singleton design pattern. There are several reasons we chose to go with these patterns. These reasons are for connection stability, control, and decoupling.
First, we chose the Singleton pattern for connection stability. A client should ever have only one connection to our server. The paradigm behind the Singleton pattern is to offer a global point of access (the instance invocation) and ensure only one instance of the object exists ever. Now, there are plenty of arguments for and against the Singleton pattern that I will not discuss here. But it is with great consideration that I decided to utilize this pattern.
We will also notice, when looking through the source code, that both the SFSClient and DummyClient are both singletons as well. This was not necessary and I will actually be re-factoring this later.
Why is it not necessary? Well, besides the GameManager being the Singleton in the game, that persists across scenes, it will act as a Toolbox. A Toolbox is a concept I read about from IBM. It is essentially a Singleton that allows access to other system controllers (managers) that would have otherwise been Singletons as well. It prevents a mass amount of Singletons being created, and allows one point of access to each instance inside of the Toolbox. For more information on the Toolbox, here is the link: https://www.ibm.com/developerworks/library/co-single/. Long story short, a Toolbox is an aggregation of Singletons.
Right, let us move away from this distraction back to the topic at hand.
The second reason we went with these patterns are control. Unity may have its own messaging system and what not for broadcasting to other objects and nested objects, but we needed something we could control in our scripts. Rather than randomly invoking some method, we wanted to create a decoupled way of having objects react to various server events. As of right now this is poorly implemented due to me. Right now there are only about five messages that the SFSClient notifies to registered observers. This is mostly because some of the messages from the server involve moving RemotePlayers or the ClientPlayer and relaying that information. However, there is one example that shows how this is intended to work and should be refactored to work.
In the project there are four interfaces put together to make the client speak with the server. These interfaces are the IEventListener (Observer), IEventMessenger(Observed), IClient, and IClientController. Each of these, with the exception of the latter, have methods that must be overridden by child objects. The reason for, as mentioned before, decoupling. In fact, any event listener and client have no idea what the SFSClient or DummyClient are, they simply know what a IClientController is. Now, if this was C++ land, I would be continuing on to tell you about all of the performance implications of utilizing pure virtual functions and v-table look ups, but this is C# land, so we just don't give a shit. Moving on, let us delve into this pattern more with and example found in the source code.
Looking at ChatGUI, we can see that it derives from MonoBehaviour (as all Unity scripts must) and IEventListener. On top of that, the class also maintains a reference to an IClientController object. This reference is pointed to the GameManager's instantiated IClientController instance (remember the Toolbox?). This instance is different depending on what game mode we are going to do. There are two types, the SFSClient which is an IClientController and the DummyClient, which is a mock server connection class. Respectively, each one is instantiated for Multi-player and Single Player.
Right, so looking at IClientController, we can see it is an aggregated Interface, a child of both IClient and IEventMessenger. This allows for a server-listener model to be implemented. This keeps us from constantly polling for events. Rather, the IClientController reaches an eventable moment, and notifies all listeners. Such events, for this example, are a chat message.
Looking at ChatGUI again, we can see that it implements the IEventListener method Notify (as it must). Its arguments are a string and an object. The string is used to pass on what kind of message is being sent (this is simple on our environment because SmartFox uses the same type of String/Object combinations and HashTables/Dictionaries for passing data around. The object is arbitrary data that is sent to the listener. Now, there is a concern here that I will go into detail with. This concern is Type Safety.
For those who don't know (mostly beginners) you can throw around arbitrary data and classes around in C# by having, as a parameter, an object. Because all classes and primitives in C# implicity are children of the object, anything can be sent as a parameter to a method. This saves time and programming logic by not having to utilize massive amounts of polymorphism for every single possible case of data passed, if it is not relevant to the object implementing the interface.
For a local example, look at IEventListener, now imagining all possible combinations of string-object parameters to be passed. Well, we would have string plus all primitives (string, float; string, int; string, float[]; etc.) and of course all classes we have. What, so, if IEventListener knows about all objects in the game, then all children know about all objects in the game, now we have a serious case of coupling. So, we pass arbitrary data. However, that is a problem with this as well, arbitrary data.
The problem with this is that we have no control over what data is given. So we could have the message type sent to the ChatGUI be of type "charmessage" but the data passed as an object could be a float value. However; the ChatGUI on a message type of "charmessage" is expecting a string type. We can see how this can cause an issue now. Luckily, in C# we can call the typeof method to instill type safety in our methods.
Now, we have not done this in the source code as of yet. It is something that needs to be implemented. This could be done using exceptions or just early returns.
As we can see, by implementing Singletons and the Observer pattern we were able to accomplish several ideas. These ideas were to work towards connection stability, control, and decoupling. By using interfaces we were able to implement these ideas and maintain a system that is significantly decoupled. It utilizes several well known design patterns to accomplish this.
No comments:
Post a Comment