Ship placement and movement, now with friends!

It was certainly longer than expected, but I successfully reached the 0.1.7-alpha milestone which officially adds preliminary multiplayer turn based ship placement and movement.

So what does that look like?

client_server_0.1.7-alpha

Oh there’s also a shiny new space skybox. These are super easy (and fun!) to render yourself using Spacescape. And to think I almost bought a skybox from the asset store.. ha!

Much has been learned about many different things, I shall touch on a few.

First, I want to talk about the game model. Something I completely missed in my original design was the notion of maintaining a server state  independent of the client game state. This became evident really quickly in two ways; trying to run a game client in the same process as the game server and trying to have a network client submit the turn ahead of the game host submitting the turn. Basically in both cases, the preview functionality was impossible to manage properly as any preview would actually impact the server’s state and trying to reset to the round’s start state was much more difficult than it needed to be. This was resolved pretty easily by maintaining a completely separate server game state that is loaded into the game field only when the server actually needs to process a turn. Then the start state, player orders and end state are all pushed out to the connected clients to enable local playback of the turn results. At the moment this means each client is really just processing the turn again locally and maintaining their own local state, which in some weird situations might actually put a client out of sync with the server, but that sounds like a problem for future Chris!

Another issue that bit me pretty early on was the default UNet channel for network communications is a ReliableSequenced channel. This limits message sizes to the MTU of the network layer. This is typically somewhere around 1500 bytes but can vary. This caused problems in my early testing of the new serialization system. As you can imagine, serializing data objects into JSON to fire across the wire can get pretty verbose. A few turns going across the wire and I was hitting an error about large message sizes. Some googling showed a few similar issues which pointed me in the right direction. I simply changed the channel of my connection to be a ReliableFragmented channel and the problem went away! I’m sure it has created me a new problem, but for now that’s good enough. One thing to keep in mind here is that when you’re mucking about with network channels, make sure your client and server are using the same channel configurations. Trust me on this one, you’ll thank me for it later.

Finally I ran in to some interesting problems with the way my movement code was working. I had originally crafted a simple Coroutine to do the dirty work across multiple frames which saved me the hassle of trying to manage state in update methods. However, when trying to get the server process a turn, I discovered that the server was ending the turn immediately upon starting it and firing off the state back to the client without actually having moved anything. Well as it turns out, I was basically processing the turn orders synchronously without waiting for the asynchronous movement to actually finish first! This turned out to be a pretty easy fix. I updated to the latest version of strangeioc which I knew had support for promises. Documentation on strangeioc promises is a little light at the moment so I’ll leave the link to the blog post about it here. Long story short, I was able to add promise support to my turn processor and it now looks like this:

public override void Execute() {
    Server.GameState.Process(GameField);
    Server.GameState.Add(Server.GameState.CurrentRound);
    Server.Send(Server.GameState.CurrentRound);
    Server.GameState.CurrentRound.Orders.Clear();
    Server.GameState.CurrentRound.StartState = new RoundState(Server.GameState.CurrentRound.EndState);

    Retain();
    Server.GameState.Process(GameField).Then(FinishUp);
}

private void FinishUp() {
    Server.Send(Server.GameState.Commit());
}

My movement coroutine now looks something like this:

private IPromise moveToPromise = new Promise();

private IEnumerator moveTo(Vector3 target) {
    var arc = new ArcPathSegment(View.transform.forward, View.transform.position, target, View.transform.up);
    float speed = View.Data.Speed;
    float p = 0f;
    float d = 0f;
    while (p < 1.0f) {
        d = speed * Time.deltaTime / arc.Length;
        p += d ;
        rigidBody.MovePosition(arc.GetPoint(p));
        View.transform.forward = arc.GetTangent(View.transform.position);
        View.Data.Position.Position = transform.position;
        View.Data.Position.Rotation = transform.rotation.eulerAngles;
       yield return null;
    }
    rigidBody.MovePosition(arc.GetPoint(1.0f));
    View.Data.Position.Position = transform.position;
    View.Data.Position.Rotation = transform.rotation.eulerAngles;
    moveToPromise.Dispatch();
 }

In case you missed it, the magic is that call to moveToPromise.Dispatch() after the coroutine ends. This triggers the fulfillment of the promise acquired in the turn processor which then fires the FinishUp method once all asynchronous movement has completed.

I’ll be honest, I’ve had trouble wrapping my head around exactly where to use promises but this seems like a pretty good fit.

That about wraps up this post. I’m not entirely sure what my next goal is but I definitely want to get working on combat sooner rather than later. That would put me very close to an actual game that could actually start to be playtested! That would be pretty cool. :)

Anyhow, until next time..

Leave a Reply