• Hey Guest! Ever feel like entering a Game Jam, but the time limit is always too much pressure? We get it... You lead a hectic life and dedicating 3 whole days to make a game just doesn't work for you! So, why not enter the GMC SLOW JAM? Take your time! Kick back and make your game over 4 months! Interested? Then just click here!

GameMaker Steam Multiplayer Hassles (Lag spikes / Stuttering)

Hello! I hope all is going well for everyone! I've been bashing my head back and forth for the longest while now with trying to create a multiplayer game.

So far, my game works and has functions like: Being able to see other players move, enemies spawning in and also moving around, but....

The classic problem I'm dealing with is lag spikes and stuttering for the clients. :(

One player acts as the server and sends all the information to the clients when things need to be updated like: When an enemy x or y has updated, when the sprite index has changed, etc.
Clients take this information and apply it to fake objects in their game so it seems real buts actually fake.

So my game is working just fine, its just that I randomly see enemies, bullets and players stutter quite a bit on the client side and I have no idea what to do at this point! D:

I've tried limiting it down to the best of my abilities with only having updates send when a value has changed but it doesn't seem to do the trick :(
If anyone has ideas on how I can limit even more data being sent, that would be great!
 

Nocturne

Friendly Tyrant
Forum Staff
Admin
It would be helpful to see an example of what is being sent in a typical update package... So share some code showing how you're sending/receiving and the contents please!
 
It would be helpful to see an example of what is being sent in a typical update package... So share some code showing how you're sending/receiving and the contents please!
Hey thanks for the response!

So I'm not sure if this is the correct way of doing this, but here's the enemy code for sending information:

This is in my enemy End Step event, the game checks to make sure you are the server before running these events too!:

Code:
    if (sprIndexServer != sprite_index) { //SPRITE INDEX
        var p = packet_start(packet_t.objectSpriteIndexUpdate);
        buffer_write(p, buffer_u16, id);
        buffer_write(p, buffer_s16, sprite_index);
        packet_send_all(p);
    }
    sprIndexServer = sprite_index;
   
    if (xScaleServer != image_xscale) { //X SCALE
        var p = packet_start(packet_t.objectXScaleUpdate);
        buffer_write(p, buffer_u16, id);
        buffer_write(p, buffer_f64, image_xscale);
        packet_send_all(p);
    }
    xScaleServer = image_xscale;

    if (yScaleServer != image_yscale) { //Y SCALE
        var p = packet_start(packet_t.objectXScaleUpdate);
        buffer_write(p, buffer_u16, id);
        buffer_write(p, buffer_f64, image_yscale);
        packet_send_all(p);
    }
    yScaleServer = image_yscale;
   
    if (imgBlendServer != image_blend) { //IMAGE BLEND COLOR
        var p = packet_start(packet_t.objectImageBlendUpdate);
        buffer_write(p, buffer_u16, id);
        //buffer_write(p, buffer_u16, obj_multiplayerObject);
        buffer_write(p, buffer_f64, image_blend);
        packet_send_all(p);
    }
    imgBlendServer = image_blend;

    if (dirServer != direction) { //DIRECTION
        var p = packet_start(packet_t.objectDirection);
        buffer_write(p, buffer_u16, id);
        buffer_write(p, buffer_s16, direction);
        buffer_write(p, buffer_u16, object_index);
        packet_send_all(p);
    }
    dirServer = direction;
   
    if (imgAngServer != image_angle) { //IMAGE ANGLE
        var p = packet_start(packet_t.objectAngle);
        buffer_write(p, buffer_u16, id);
        buffer_write(p, buffer_f32, image_angle);
        packet_send_all(p);
    }
    imgAngServer = image_angle;
   
    if (depthServer != depth) { //DEPTH
        #region SERVER Send Animation Update
       
        var p = packet_start(packet_t.objectDepth);
        buffer_write(p, buffer_u16, id);
        buffer_write(p, buffer_f32, depth);
        packet_send_all(p);
       
        #endregion
    }
    depthServer = depth;
   
    if(xprevious != x) { //X POSITION
        var p = packet_start(packet_t.objectPosX);
        buffer_write(p, buffer_u16, id);
        buffer_write(p, buffer_f32, x);
        packet_send_all(p);
    }
   
    if(yprevious != y) { //Y POSITION
        var p = packet_start(packet_t.objectPosY);
        buffer_write(p, buffer_u16, id);
        buffer_write(p, buffer_f32, y);
        packet_send_all(p);
    }
So I'm not sure if this is the way I should be tackling packets and such, but it seems to work, just with random lag and stutter on the clients side.

With every piece of data sent to the clients, the packet includes an ID so it can apply the correct info to the fake objects on their screen.

I have a script that can handle information being sent in from the server, so here is just an example of how my system works:

Code:
        case packet_t.objectPosX:
            // update the client-side player instance:
            var _id = buffer_read(b, buffer_u16);
            var _x = buffer_read(b, buffer_f32);
            with(obj_multiplayerObject) if (multi_id == _id) {
                x = _x;
            }
        break;
            
        case packet_t.objectPosY:
            // update the client-side player instance:
            var _id = buffer_read(b, buffer_u16);
            var _y = buffer_read(b, buffer_f32);
            with(obj_multiplayerObject) if (multi_id == _id) {
                y = _y;
            }
        break;
I hope this can help!
 

O.Stogden

Member
Have you tried running the GMS profiler on the client and see if there's an unusually high time spent on a step/draw event? It might help narrow it down further. This seems pretty similar to how I handle networking though, it doesn't seem terribly off at first glance.

I presume the client side script is run in an asynchronous event?

Also as a side note, I'm not sure a 32-bit float is necessary for x,y and depth, you could possibly use s16 for the depth (-32000 to +32000) and u16 for x and y considering the x and y will likely never be below 0. This would cut network traffic for those two things.

Image_blend might be able to work as a u32, rather than an f64, as I don't think image_blend ever goes below 0. Although seems it might default to -1, so might be worth testing.

Likewise image_xscale and yscale could be s8, instead of f64, unless you're really making an object grow more than 128 times its size.
 
Have you tried running the GMS profiler on the client and see if there's an unusually high time spent on a step/draw event? It might help narrow it down further. This seems pretty similar to how I handle networking though, it doesn't seem terribly off at first glance.

I presume the client side script is run in an asynchronous event?

Also as a side note, I'm not sure a 32-bit float is necessary for x,y and depth, you could possibly use s16 for the depth (-32000 to +32000) and u16 for x and y considering the x and y will likely never be below 0. This would cut network traffic for those two things.

Image_blend might be able to work as a u32, rather than an f64, as I don't think image_blend ever goes below 0. Although seems it might default to -1, so might be worth testing.

Likewise image_xscale and yscale could be s8, instead of f64, unless you're really making an object grow more than 128 times its size.
Thank you for the response! I have not tried running the profiler to check the step/draw events so I'll definitely check that out!

So I've been using YellowAfterLifes steam lobby example and built off from there, fixed a few things, but from what I'm seeing is that the client-side script is being run in a End Step Event

I think the reason I have the x, y and depth like that is because I had some stuttering when the enemies were moving on the client-side

Reason being is that my enemies use the lerp() function to move around sometimes and it spits out decimals, but other buffer types don't support float values so I found this worked best unless there's a fix

I'm also thinking that I can change the buffer data type for the depth or maybe even make it client-sided
 

O.Stogden

Member
The decimals being spat out could be handled server-side by using round() to round them to the nearest integer before sending them to a client. Network bandwidth is probably more precious than CPU on the server, so you could try that.

For example:
Code:
buffer_write(p, buffer_u16, round(x));
Depth probably doesn't need to be sent across a network, as it usually doesn't change per object. Depends on your game, but usually it can just be hardcoded on the client, yeah.

Definitely have a look at your step event in particular then, and see if it's using more processing time than you would expect. I'm not sure how much detail it'll give considering you aren't using many functions here, but hopefully it'll point out something useful. I guess the lag might be because Game Maker traditionally uses the Asynchronous events for networking, not the step, but that's a limitation of using the Steamworks API, as GameMaker has to use YAL's extension for that, so he would be restricted to the step event I imagine.
 
The decimals being spat out could be handled server-side by using round() to round them to the nearest integer before sending them to a client. Network bandwidth is probably more precious than CPU on the server, so you could try that.

For example:
Code:
buffer_write(p, buffer_u16, round(x));
Depth probably doesn't need to be sent across a network, as it usually doesn't change per object. Depends on your game, but usually it can just be hardcoded on the client, yeah.

Definitely have a look at your step event in particular then, and see if it's using more processing time than you would expect. I'm not sure how much detail it'll give considering you aren't using many functions here, but hopefully it'll point out something useful. I guess the lag might be because Game Maker traditionally uses the Asynchronous events for networking, not the step, but that's a limitation of using the Steamworks API, as GameMaker has to use YAL's extension for that, so he would be restricted to the step event I imagine.
Yeah so I noticed something alarming when running the game as the client 0_0

As soon as the level loaded up and the game was playing, the fps went from 1500 down to 500 average!

I tested it out on single player and the fps dropped from 1500 to 1100 average

So I think something is going very wrong here
 

O.Stogden

Member
The profiler should hopefully let you know which event is causing the loss of FPS. I'd assume it's that client step event though, given that it only happens on the client. It's just a matter of what in the step event might cause it.

I'd guess something might be being set every step and causing lag, but I couldn't say without seeing it.
 
The profiler should hopefully let you know which event is causing the loss of FPS. I'd assume it's that client step event though, given that it only happens on the client. It's just a matter of what in the step event might cause it.

I'd guess something might be being set every step and causing lag, but I couldn't say without seeing it.
Yeah I believe this might be where the issue leads into. I setup some breaker points and it narrowed it down to here

Code:
// receive packets:
while (steam_net_packet_receive()) {
    var from = steam_net_packet_get_sender_id();
    var b = inbuf;
    if (lobby_is_owner) {
        if (ds_map_exists(net_map, from)) {
            // a packet from someone familiar
            buffer_seek(inbuf, buffer_seek_start, 0);
            steam_net_packet_get_data(inbuf);
            packet_handle_server(from);
        } else {
            // sender unknown, require a handshake to establish connection
            // make sure that the packet is big enough to be a handshake-packet:
            if (steam_net_packet_get_size() < 6) continue; // (skip this packet)
            // make sure that the player is actually in our lobby, however:
            var i, n;
            n = steam_lobby_get_member_count();
            for (i = 0; i < n; i++) {
                if (steam_lobby_get_member_id(i) == from) break;
            }
            if (i >= n) continue; // (skip this packet)
            //
            buffer_seek(inbuf, buffer_seek_start, 0);
            steam_net_packet_get_data(inbuf);
            packet_handle_auth(from);
        }
    } else {
        // as a client, only accept packets from server:
        if (from != lobby_owner) continue;
        //
        buffer_seek(inbuf, buffer_seek_start, 0);
        steam_net_packet_get_data(inbuf);
        packet_handle_client(from);
        joining_lobby = false;
    }
}
Unfortunately, I didn't write this so I don't 100% know what's going on but hopefully you can see if somethings wrong here
 
The profiler should hopefully let you know which event is causing the loss of FPS. I'd assume it's that client step event though, given that it only happens on the client. It's just a matter of what in the step event might cause it.

I'd guess something might be being set every step and causing lag, but I couldn't say without seeing it.
My profiler says that the steam object step event is taking up 40% 0_0
 

O.Stogden

Member
Yeah, it's hard for us to tell, there's so many scripts referenced here that are custom and not part of GM's usual code, so it doesn't make much sense to me.

Someone familiar with the extension or YAL himself might be able to offer more assistance...
 
Top