Smooth movement over network

Hello, I’m trying to make a simple multiplayer TDS game with LiteNetLib library.

I’m new to networking so I don’t really know how everything works. I have read some articles about it but I’m still unable to fix my problem.

The problem is that when I send position of a character to the server and try to draw it there, it works but it’s not as smooth as at the client window. I use localhost where the data transmition and receive should be instant am I right?

This is the client code:

    private readonly NetPacketProcessor _netPacketProcessor = new NetPacketProcessor();
    private NetManager _client;
    Vector2 position;

    public Client()
    {
        position = new Vector2(200, 200);
        _netPacketProcessor.RegisterNestedType<VectorStruct>();
        _netPacketProcessor.SubscribeReusable<ClientPacket, NetPeer>(OnReceiveEvent);
        EventBasedNetListener clientListener = new EventBasedNetListener();

        _client = new NetManager(clientListener);
        _client.Start();
        _client.Connect("localhost", 9058, "Key");
    }

    public void Update(GameTime gameTime)
    {
        if (Keyboard.GetState().IsKeyDown(Keys.W))
        {
            position.Y -= 150 * (float)gameTime.ElapsedGameTime.TotalSeconds;
        }
        else if (Keyboard.GetState().IsKeyDown(Keys.A))
        {
            position.X -= 150 * (float)gameTime.ElapsedGameTime.TotalSeconds;
        }
        else if (Keyboard.GetState().IsKeyDown(Keys.S))
        {
            position.Y += 150 * (float)gameTime.ElapsedGameTime.TotalSeconds;
        }
        else if (Keyboard.GetState().IsKeyDown(Keys.D))
        {
            position.X += 150 * (float)gameTime.ElapsedGameTime.TotalSeconds;
        }
        var vectorStruct = new VectorStruct
        {
            positionX = position.X,
            positionY = position.Y
        };
        _netPacketProcessor.Send(_client, new ClientPacket { VectorStruct = vectorStruct }, DeliveryMethod.ReliableOrdered);   
    }
}

This is the server code:

    private readonly NetPacketProcessor _netPacketProcessor = new NetPacketProcessor();
    private NetManager _server;
    private User user; // represents the character

    public Server()
    {
        _netPacketProcessor.RegisterNestedType<VectorStruct>();
        _netPacketProcessor.SubscribeReusable<ClientPacket, NetPeer>(OnReceiveEvent);
        EventBasedNetListener serverListener = new EventBasedNetListener();
        
        serverListener.ConnectionRequestEvent += request =>
        {
            request.AcceptIfKey("Key");
            user = new User(new Vector2(200, 200), request.RemoteEndPoint.ToString());
        };

        serverListener.NetworkReceiveEvent += (peer, reader, method) =>
        {
            _netPacketProcessor.ReadAllPackets(reader, peer);
            reader.Recycle();
        };

        _server = new NetManager(serverListener);
        _server.Start(9058);
    }
    
    public void Update(GameTime gameTime)
    {
        _server.PollEvents();
    }

    private void OnReceiveEvent(ClientPacket packet, NetPeer netPeer)
    {
        user.position.X = packet.VectorStruct.positionX;
        user.position.Y = packet.VectorStruct.positionY;
    }

The Game1.cs:

    protected override void Update(GameTime gameTime)
    {
        if (isClient)
        {
            client.Update(gameTime);
        }
        else if (isServer)
        {
            server.Update(gameTime);
        }
        base.Update(gameTime);
    }
    // everything else isn't important

I have also tried moving the character through adding velocity to each axis but it also didn’t work. Do I have to somehow sync the client and server?

Hello :slight_smile:

So I’m not a networking expert at all. I have very little practical experience, only a bit of theory I’ve gleaned over the years. Feel free to take all this with a grain of salt :slight_smile:

I think the reason your client is smooth but your server isn’t is because you’re sending direct positions over the network to the server. So your client is behaving as normal, but the server has to get the positions as a response, which comes with network delay that is probably variable.

I think a typical tactic here is, instead of sending positions, send the inputs and let the other client (ie, server) calculate where the client will be. So your client says, “Hey, server, I’m pressing right!” and then proceeds to update it’s local state with whatever should happen when right is pressed. When the server gets the message that the client has pressed right, it too proceeds to do whatever should be done when right is pressed. Likewise when right is released.

That’s probably a good enough start for now, but eventually you’ll probably want to add some code to the server to check to see if the client’s position is where the server thinks it should be. There’s a lot of ways to do this. Recently I’ve been looking into something called Rollback Networking that a friend was talking about. If that type of thing is applicable, you could check it out. It will help make sure that the client/server remain in sync.

Anyway, I know that’s pretty light on detail but I hope it helps :slight_smile:

1 Like

Hi, thanks a lot for your answer.

I’ve just tried what you suggested, but unfortunately it didn’t work. I even added a very simple input prediction, but it’s still not smooth at all.

When I was debugging, I realized that the problem might be that the server sometimes doesn’t read or receive any data (I don’t know which one of those) and then it reads all the packets it didn’t read when it’s supposed to. I fixed this by making the simple input prediction, but as I said, it still isn’t smooth.

Later, I talked about it with a friend, and he advised me to try another library. I want to stick with LiteNetLib, but I’m really lost and out of ideas on how to fix it.

If you want to put some more time into it, I’ve put the whole project here.

Thanks again for your answer.

While this seems weird for something I’d imagine you’re testing in a local network, it also makes me wonder about the implementation.

You mentioned that the movement isn’t smooth at all, but at this point I expect your server to only get two meaningful messages, input is pressed and input is released. So simplifying it to just moving right, the client should send a message to the server when Keys.D transitions from not pressed to pressed, then again from pressed to not pressed.

Your server should start moving the object to the right when it receives a message that right is pressed, then stop moving it to the right once it receives a message that right is released.

While this is by no means robust, I would expect it to result in smooth movement of the remote client on the server instance. If it doesn’t, it kind of suggests that maybe more state messages are being transmitted than you expect?

I took a look at your code and yea, I think you’re broadcasting a packet every single frame that the key is down, which will be a lot of packets. Try changing it to just transmit a packet on the state change boundaries and see what happens?

2 Likes

I didn’t think of it this way, it finally worked and fixed the whole problem. You saved me a lot of struggling.

Thanks a lot

1 Like

No worries, glad I could help!

Please do keep in mind that this is just a super simplified implementation. Doing it this way makes it very possible for your clients to get out of sync since there’s no guarantees that the delay between the action and when it’s transmitted to the server will be consistent.

Google rollback networking, that might be something that is applicable to you. I guess it’s good for small player count peer to peer networking models, such as brawl games (ie, Smash Bros). I suppose if you wanted a super easy “fix” just to get by in the short term, you could periodically sync state to make sure everybody is where they’re supposed to be.

At some point you’ll probably want to shift the “source of truth” over to your server as well.

Anyway, that’s awesome you got something working! Further than I’ve ever gotten to be sure. Nicely done :slight_smile:

1 Like