|
|
some multiplayer thoughts
#400063
04/25/12 10:59
04/25/12 10:59
|
Joined: Sep 2003
Posts: 6,861 Kiel (Germany)
Superku
OP
Senior Expert
|
OP
Senior Expert
Joined: Sep 2003
Posts: 6,861
Kiel (Germany)
|
I was asked about some tips for client-side multiplayer movement and decided to post my thoughts here, maybe someone will find this useful. The following text and code is taken from an old Skype conversation:
at first I only tried to update xyz and pan 8times a second or so, and interpolate the rest. but I didn't get it as smooth as it should be and it used too much traffic bandwidth so I've implemented a variable/ skill like my.keys and set its bits according to the player input, f.i. as follows:
my.keys = key_w + key_s*2 + key_a*4 if(my.keys != my.old_keys) { my.old_keys != my.keys; send_skill(my.keys,SEND_ALL); }
you see, the skill only gets send when the player presses a different button/ releases a button. the server transmits obviously the skill to all other clients. then in the entity action the skill is used to move the client (same action on client and server), f.i. if(my.keys & 4) move left as you turn the player with the mouse and the pan value probably almost changes every frame, you cannot do it the same way. instead, I use something as follows:
my.pan_update = maxv(my.pan_update -time_step,0); if(!my.pan_update && abs(ang(my.old_pan-my.pan)) > 1) { my.pan_update = 2; my.old_pan = my.pan; send_skill(my.old_pan,SEND_ALL); // do not send my.pan here }
the code updates the old_pan skill at most 8 times a second and only when the pan changes significantly (on the sever you have to transmit the skill of course to the other players, too) in the player-animation (of entities that are not controlled by the player), you simply write something as follows:
my.pan += ang(my.old_pan-my.pan)*time_step; this should almost be sufficient to have a smooth multiplayer experience. the problem is, because of the approximations, after a little time the players can be on different locations on different pcs. that's why you have to send the xyz position from time to time to the other clients and smoothly blend between the two positions. when you do that smoothly, you will run into another problem. when you simply move the entity to let's say my.dplay_x (that's a skill vector), in detail:
my.pos_update = maxv(my.pos_update -time_step,0); if(!my.pos_update && vec_dist(my.x,my.dplay_x) > 8) { vec_set(my.dplay_x,my.x); my.pos_update = 8; send_skill(my.dplay_x,SEND_ALL | SEND_VEC); } ...
if( vec_dist(my.x,my.dplay_x) > 8) { my.x += (my.dplay_x-my.x)*0.5*time_step; my.y += (my.dplay_z-my.z)*0.5*time_step; my.z += (my.dplay_z-my.z)*0.5*time_step; // you should calculate the z-position with c_trace, not with such a formula }
with this smooth alignment, the player will not be able to move forward until he reaches the position so you have to calculate a direction vector instead when you receive a new dplay_x position and apply the force over a time:
vec_diff(temp,my.dplay_x,my.x); my.skill1 = 4; while(my.skill1 > 0) { move entity by temp*time_step/4; my.skill1 -= time_step; wait(1); }
this should correct the position pretty accurately, and it took a while to come up with this idea
EDIT: When you use the native multiplayer engine, make the following settings:
dplay_entrate = 100; dplay_smooth = 0; dplay_localfunction = 2;
In entity-functions: while(my.client_id < 0) wait(1); my.smask |= NOSEND_ALPHA | NOSEND_AMBIENT | NOSEND_ORIGIN | NOSEND_ANGLES | NOSEND_ATTACH | NOSEND_COLOR | NOSEND_FLAGS | NOSEND_FRAME | NOSEND_LIGHT | NOSEND_SCALE | NOSEND_SKIN | NOSEND_SOUND;
Last edited by Superku; 04/25/12 11:21.
"Falls das Resultat nicht einfach nur dermassen gut aussieht, sollten Sie nochmal von vorn anfangen..." - Manual Check out my new game: Pogostuck: Rage With Your Friends
|
|
|
Re: some multiplayer thoughts
[Re: alibaba]
#400099
04/25/12 19:19
04/25/12 19:19
|
Joined: May 2009
Posts: 5,367 Caucasus
3run
Senior Expert
|
Senior Expert
Joined: May 2009
Posts: 5,367
Caucasus
|
|
|
|
Re: some multiplayer thoughts
[Re: 3run]
#400121
04/26/12 06:10
04/26/12 06:10
|
Joined: May 2009
Posts: 5,367 Caucasus
3run
Senior Expert
|
Senior Expert
Joined: May 2009
Posts: 5,367
Caucasus
|
|
|
|
Re: some multiplayer thoughts
[Re: 3run]
#400150
04/26/12 14:06
04/26/12 14:06
|
Joined: Sep 2003
Posts: 6,861 Kiel (Germany)
Superku
OP
Senior Expert
|
OP
Senior Expert
Joined: Sep 2003
Posts: 6,861
Kiel (Germany)
|
my.pan_update = maxv(my.pan_update - time_step, 0);
if(!my.pan_update && abs(ang(my.old_pan - my.pan)) > 1)
{
my.pan_update = 2;
my.old_pan = my.pan;
send_skill(my.old_pan, SEND_ALL); // do not send my.pan here
}
// for visual rotation:
my.pan += ang(my.old_pan - my.pan) * time_step;
Yes, but keep in my that you only execute the last line of code on the clients that are not controlling the entity. then, on the machine which doesn't own created entity, we move it like this:
if(vec_dist(my.x, my.dplay_x) > 8)
{
my.x += (my.dplay_x -my.x) * 0.5 * time_step;
my.y += (my.dplay_y - my.y) * 0.5 * time_step;
my.z += (my.dplay_z - my.z) * 0.5 * time_step;
}
No, this was a (misleading) example how you should not do it, see my first post for a reason. Instead, only use the "counter" while loop method:
vec_diff(temp, my.dplay_x, my.x);
my.counter = 4;
while(my.counter > 0)
{
temp.x = time_step / 4;
temp.y = time_step / 4;
temp.z = time_step / 4;
c_move(my, nullvector, temp, GLIDE);
my.counter -= time_step;
wait(1);
}
Right now this does not make a lot of sense (no offense), try something as follows instead:
void player_interpolate_move()
{
VECTOR temp, move_vec;
proc_kill(5);
vec_diff(move_vec,my.dplay_x,my.x);
my.counter = 4;
while(my.counter > 0)
{
vec_set(temp,move_vec);
vec_normalize(temp,0.25*time_step);
c_move(my, nullvector, temp, IGNORE_SPRITES | IGNORE_MODELS | IGNORE_WORLD);
my.counter -= time_step;
wait(1);
}
}
void player_interpolate()
{
if(my.dplay_x || my.dplay_y || my.dplay_z)
{
player_interpolate_move();
vec_set(my.dplay_x,nullvector);
}
my.pan += ang(my.old_pan-my.pan)*0.5*time_step;
}
Then your player code could/ should like this:
void player_interpolate_move()
{
VECTOR temp, move_vec;
proc_kill(5);
vec_diff(move_vec,my.dplay_x,my.x);
my.counter = 4;
while(my.counter > 0)
{
vec_set(temp,move_vec);
vec_normalize(temp,0.25*time_step);
c_move(my, nullvector, temp, IGNORE_SPRITES | IGNORE_MODELS | IGNORE_WORLD);
my.counter -= time_step;
wait(1);
}
}
void player_interpolate()
{
if(my.dplay_x || my.dplay_y || my.dplay_z) // you'd better use another skill here like my.dplay_do_interpolation or something like that and reset it after the function call below
{
player_interpolate_move();
vec_set(my.dplay_x,nullvector);
}
my.pan += ang(my.dplay_pan-my.pan)*0.5*time_step; // better use a different skill than old_pan, otherwise it gets difficult to transmit the information from one client to the server and to a second client
}
void player_input()
{
my.keys = key_w + key_s*2 + key_a*4 + key_d*8;
// change pan here, too
}
void player_move()
{
move (using my.keys) and animate;
}
void player_update()
{
update my.keys, my.dplay_pan and position
}
action act_player()
{
while(my.client_id < 0) wait(1);
my.smask |= NOSEND_ALPHA | NOSEND_AMBIENT | NOSEND_ORIGIN | NOSEND_ANGLES | NOSEND_ATTACH | NOSEND_COLOR | NOSEND_FLAGS | NOSEND_FRAME | NOSEND_LIGHT | NOSEND_SCALE | NOSEND_SKIN | NOSEND_SOUND;
while(1)
{
if(my.client_id == dplay_id) player_input();
else player_interpolate();
player_move();
if(my.client_id == dplay_id || connection != 2) player_update();
wait(1);
}
}
Last edited by Superku; 04/26/12 14:06.
"Falls das Resultat nicht einfach nur dermassen gut aussieht, sollten Sie nochmal von vorn anfangen..." - Manual Check out my new game: Pogostuck: Rage With Your Friends
|
|
|
|