Wednesday, April 23, 2014

Unity / SmartFox Senior Project : Halfway Point

Today marks the halfway marker for our Unity project: Starbound Aces. It has been a long 8 weeks of producing documentation and an Alpha version. Playable here: Play Starbound Aces

I want to spend this time recapping all of the work done for the game and what I would have done differently in regards to the programming architecture of the game. Then I would like to explain what will happen moving forward from here.

In regards to the recap there are several subjects I would like to cover. These subjects are server extension design, database design, and the client side design.

The server extension design was originally intended to follow a simple authoritative design in which client data was verified by various classes. These classes I called game drivers. Each class was responsible for collecting data from each client. This was implemented fairly simply but did not achieve its ultimate purpose. Each of these classes were to have validation methods built in that accepted or rejected client data by virtue of 'is it possible'. For instance, if the client says their position is at a given point, the ship validation (containing all information for movement such as acceleration, velocity, position, and rotation) would determine whether this was possible in the time passed and the given latency. If not, it would tell the client that their ship is elsewhere. When implementing this method however, even without the validation, it caused a lot of jumping around client side. This leads into what I would have done differently in regards to the server extension design.

What I would have done differently in regards to server extension design is completely simulate, and drive, the game from the server. What does this mean? Well, it would imply all physics, movement, game logic, events, combat, everything, would be handled server side. Essentially, the game core systems to drive it would have been programmed server side. The only responsibility of Unity would be to collect the data, render, connect, give user feedback, and get user input.

The database design of the project was small. There are only three tables and two that are actually used. The two tables that are used are for the user and the user scores. This is simple enough and was just enough to accomplish the lowest core requirements of the game. This I have no problem with and went without a hitch.

There are a few, more ambitious, ways I would have used the database.

Now, what I wanted to have done is much more in depth. On top of the scores and users, I wanted to add in functionality for the game table. The game table would hold metadata about each game played. This would allow new people joining the lobby to see a game created before they entered. It would also allow a player to see their past game statistics. Finally, I would have added more tables for ship configurations. This would hold all data for each weapon, projectile, and ship hull available. It would then allow players to set configurations when connected to the server and pick one for the game they are about to join.

The client design I turned out to work well with what was originally planned with a few modifications. The SmartFox Server singleton class did the grunt of the work. Its main responsibility was to connect, login, handle events, and forward data between client and server. This worked out excellently without any problems and worked as intended.

There are few things I would have changed. One would have been to create smaller scripts based on network actions. These scripts would look similar to the one I added last week called NetworkTransform:

public enum NetworkObjectType {
    PLAYER = 0,
    PROJECTILE
}

public enum CommunicationType {
    SEND_ONLY,
    RECEIVE_ONLY,
    SEND_AND_RECEIVE
}


public class NetworkTransformer : MonoBehaviour, IEventListener {

    public NetworkObjectType type;
    public CommunicationType commType;

    private int networkId = -1;
    private string stype;
    private IClientController server;


// Use this for initialization
void Start () {
        server = GameManager.gameManager.ClientController;
        server.Register(this);
        switch (type) {
            case NetworkObjectType.PLAYER:
                stype = "player";
                break;

            case NetworkObjectType.PROJECTILE:
                stype = "projectile";
                break;

            default:
                break;
        }
}

    public int NetworkId {
        get {
            return networkId;
        }
        set {
            networkId = value;
        }
    }
// Update is called once per frame
void Update () {
        if (commType == CommunicationType.SEND_ONLY || commType == CommunicationType.SEND_AND_RECEIVE) {
            SendData();
        }
}

    private float delay = 0.1f;
    private float timepassed = 0.0f;
    private void SendData() {
        timepassed += Time.deltaTime;
        if (timepassed >= delay) {
            switch (type) {
                case NetworkObjectType.PLAYER:
                    SendPlayerData();
                    break;

                case NetworkObjectType.PROJECTILE:
                    SendProjectileData();
                    break;

                default:
                    break;
            }
            
            timepassed = 0.0f;
        }
    }

    private void SendPlayerData() {
        Dictionary<string, object> data = new Dictionary<string, object>();
        data.Add("transform", transform);
        data.Add("type", stype);
        server.Send(DataType.TRANSFORM, data);
    }

    private void SendProjectileData() {
        Dictionary<string, object> data = new Dictionary<string, object>();
        data.Add("transform", transform);
        data.Add("type", stype);
        data.Add("networkId", networkId);
        server.Send(DataType.TRANSFORM, data);
    }

    public void Notify(string eventType, object o) {
        if (commType == CommunicationType.RECEIVE_ONLY || commType == CommunicationType.SEND_AND_RECEIVE) {
            switch (eventType) {

                case "transform":
                    handleTransform(o);
                    break;

                default:
                    break;
            }
        }
    }

    void OnDestroy() {
        GameManager.gameManager.ClientController.Unregister(this);
    }

    private void handleTransform(object o) {
        Dictionary<string, object> data = o as Dictionary<string, object>;
        int id = (int)data["id"];

        if (id != networkId) {
            return;
        }

        float px = (float)data["position.x"];
        float py = (float)data["position.y"];
        float pz = (float)data["position.z"];
        float rx = (float)data["rotation.x"];
        float ry = (float)data["rotation.y"];
        float rz = (float)data["rotation.z"];
        float rw = (float)data["rotation.w"];

        transform.position = new Vector3(px, py, pz);
        transform.rotation = new Quaternion(rx, ry, rz, rw);
    }
}

This class was hacked in to resolve issues where the SmartFox instance on the client side was doing too much management of data and resolving who gets what data rather than forwarding its data as originally designed. In fact, I would dare say I would have created a base script for this design that determined whether an object was send, receive, or send and receive only, then create a class for each type of network event possible. Right now, a lot of the scripts are tied to specific functionality and make reuse virtually non-existent - poor OOP design.

Moving forward we have a lot of plans for the game in regards to our class. A lot of the work to be done is related to the features of the game. So, unfortunately, most of what I said before of how I should have/ wanted to do things will not be implemented in this game. So, here is a list of what I need to get done, but I will not go into long winded detail right now. Next week though, I will explain how I will go about this when my final class starts, and the last 8 weeks of this project. Here is what I plan on getting done for next period:
  • Fix room leaving errors on server side
  • Fix users attempting to double join rooms
  • Email response to activate accounts
  • Clean up all GUIs in the game
  • Implement full database for game creation
  • Create game waiting lobby so players can queue before going into a game
  • Generating random space debris
  • Fix messaging between client and server as redundant and 'dead' data is being sent.

No comments:

Post a Comment