|
Re: multiplayer game development blog
[Re: ulf]
#72168
05/23/06 21:20
05/23/06 21:20
|
Joined: Aug 2005
Posts: 1,012 germany, dresden
ulf
OP
Serious User
|
OP
Serious User
Joined: Aug 2005
Posts: 1,012
germany, dresden
|
23.05.06 just a little update tonight. i was in erfurt the last weekend, it was a very nice visit. lots of old little medieval buildings, a bridge where on both sides are houses so if you walk in the middle you dont notice that its a bridge. was very nice, i can recommend it to you. then when i came back i finally got the result of the last test i wrote and i passed it! it was hell of a test and i didnt thought it turned out this way but this is very good news for me! i did a little work on the project today. what i did was fix the spawning. because i set nosend to off. i had to make shure that at the client the other entities are correctly displayed. so i sent the coordinates whenever a new player is created to all clients. this is done with a ent_next while loop inside the players action at the server just bevore the statemachine enters the while(1) loop. i also implented an idea i had while ago. our starter will generate a unique id and pass it to the server via -d parameter. then all clients have to start their instance of the game with the same unique id in the -d parameter. if they dont they get booted. this way i assure that only the right players can join the right game from our launcher. at least i think so at the moment so now to sum up, the chat works, joining/leaving, entity player creating and simple player movement. the next things iam gonna do are probably some fun ones. i think ill implent the basic stat system and the inventory next. maybe ill even add a little npc and test some fighting system ideas. theres plenty left to do for me. another thing i gotta to soon is implent the level load for xpresso's outstanding editor ipagED. ill let you know once i get the next results. oh and i have to keep my promise and post my movement code here. ill comment it a bit better soon and do so. until then, have a nice week!
|
|
|
Re: multiplayer game development blog
[Re: Garrettwademan]
#72171
05/29/06 12:05
05/29/06 12:05
|
Joined: Aug 2005
Posts: 1,012 germany, dresden
ulf
OP
Serious User
|
OP
Serious User
Joined: Aug 2005
Posts: 1,012
germany, dresden
|
29.05.06 okay. today as promised the movementcode. please note that this is the code i currently use. it will not work if you copy and paste it to your projects. however you may study it to see how it works for me. in short, i move the player at the host with ent_move and send positions with send_skill using SEND_RATE 4times a second to the client. the client then interpolates between the positions. the interpolation code is very similar to those william posted in this thread. the skelleton of the code is similar to locoweeds tutorial. the project is coming along niceley. right now i tested a simple inventory code so players both the client and host can pick up items dropped by npcs or other players. i do this by first creating an item with ent_create giving it an item_id wich defines wich item it is. then i make it sensitive for click. if its clicked at the client i set the skill ._player_number_wants_to_pick_me_up to the playernumber of the player who clicked it and send this skill to the server. the server then puts this item in the inventory skill slot of the player and sends back that its done to all players! next things iam gonna do is making this work with movable panels so players can drop items with the mouse once they are in the inventory. but heres the movement code i promised, have fun with it! note: it may not be perfectly clean or bugfree, please let me know if you find some majore mistakes. thx Code:
// sets all nosends, because setting my.nosend = on causes problems in 6.4.x function set_nosend() { my.nosend_frame = on; my.nosend_alpha = on; my.nosend_ambient = on; my.nosend_color = on; my.nosend_light = on; my.nosend_uv = on; my.nosend_origin = on; my.nosend_angles = on; my.nosend_flags = on; my.nosend_scale = on; my.nosend_skin = on; my.nosend_sound = on; }
// function for player events on the server function real_player_event() {
// client disconnected if (EVENT_TYPE == EVENT_DISCONNECT) { // do this to keep the number of players in order gl_number_of_players -= 1; // decrement number of players
// go through all entities, any player # that was above mine, decrement his player number you = ent_next(NULL); while(you != NULL) { if(you._player_number > my._player_number) { you._player_number -= 1; send_skill(you._player_number, SEND_ALL); } you = ent_next(you); }
send_var(gl_number_of_players); // let everyone know new number of players ent_remove(me); // remove ent of player that quit } if (EVENT_TYPE == EVENT_CLICK) { } IF (EVENT_TYPE == EVENT_TOUCH) { MY.LIGHT = ON; MY.RED = 255; MY.GREEN = 0; MY.BLUE = 0; RETURN; } IF (EVENT_TYPE == EVENT_RELEASE) { MY.LIGHT = OFF; RETURN; }
}
// event handler for clients function real_player_local_event() { if (EVENT_TYPE == EVENT_CLICK) { } IF (EVENT_TYPE == EVENT_TOUCH) { MY.LIGHT = ON; MY.RED = 255; MY.GREEN = 0; MY.BLUE = 0; RETURN; } IF (EVENT_TYPE == EVENT_RELEASE) { MY.LIGHT = OFF; RETURN; }
}
// local entity function for getting click and so on action real_player_local() {
my.invisible = on; sleep(1);
// get faction and send to server but only for the own player instance if(me == player) { my._faction = id_faction; send_skill(my._faction, 0); my._race = id_race; send_skill(my._race, 0); }
var calc_angle[3]; var move[3]; var move_old[3]; var length; // range from real to target pos var vecfrom[3]; // temp var vecto[3]; // temp var temp_loc[3]; // temp
my.enable_click = on; my.enable_touch = on; my.enable_release = on;
my.event = real_player_local_event;
my.passable = on;
// do this to assure players are in mode_stand at start my._receive_x = my.x; my._receive_y = my.y; my._receive_z = 1; // trace to geht right height to terrain vecFrom.x =my.x; vecFrom.y =my.y; vecFrom.z = 200;
vec_set(vecTo,VecFrom); vecTo.z = -200;
c_trace(vecFrom,vecTo, IGNORE_ME|IGNORE_PASSABLE|IGNORE_CONTENT|IGNORE_SPRITES|USE_POLYGON); vec_set(temp_loc,vecTo); my.z = target.z + 32;
// start animation loop animate();
// intially set animation to stand, this will be changed below if necessary my._animation_state = animation_state_STAND;
my.invisible = off;
sleep(1); // fix for bug when players didnt move my._mode = mode_stand;
while(1) { my._prev_anim_state = my._animation_state; // save animation state //stand if(my._mode == mode_stand) { my._animation_state = animation_state_STAND; // new different hostposition incoming -> go move there if(my._receive_z == 0) { my._disttotarget = abs(my.x - my._receive_x) + abs(my.y - my._receive_y); if(my._disttotarget >= 35) { my._mode = mode_walk; } } } // moving if(my._mode == mode_walk) { my._animation_state = animation_state_WALK;
my._disttotarget = abs(my.x - my._receive_x) + abs(my.y - my._receive_y);
if (my._disttotarget >= 12) { // interpolate movement vec_set(move.x, my._receive_x); if(move_old.x != move.x) { vec_set(length.x, move.x); vec_sub(length.x, my.x); vec_set(temp.x, move.x); vec_sub(temp.x, my.x); vec_to_angle(calc_angle.pan, temp.x); } vec_normalize(length, 1); my.x += length.x * (my._movespeed -0.8 ) * time; my.y += length.y * (my._movespeed -0.8 ) * time; vec_set(temp.x, nullvector); vec_set(move_old.x, my._receive_x); // turn if((my.pan < calc_angle.pan + 10) || (my.pan > calc_angle.pan - 10)) { my.pan = my.pan + ang(calc_angle.pan-my.pan) * 0.5 * time ;//i am turning } } // prevent stopping if ((my._disttotarget <12) && (my._receive_z == 0)) { my.x += length.x * (my._movespeed -1 ) * time; my.y += length.y * (my._movespeed -1 ) * time; } // trace to geht right height to terrain vecFrom.x =my.x; vecFrom.y =my.y; vecFrom.z = 200; vec_set(vecTo,VecFrom); vecTo.z = -200; c_trace(vecFrom,vecTo, IGNORE_ME|IGNORE_PASSABLE|IGNORE_CONTENT|IGNORE_SPRITES|USE_POLYGON); vec_set(temp_loc,vecTo); my.z = target.z + 32; // are we near the last received hostposition? and z is 1 if ((my._disttotarget < 12) && (my._receive_z == 1)) { my._mode = mode_stand; } }
wait(1); }
}
// action for the player is run at the server action real_player {
my.invisible = on; // set this so others dont see me until created
var dire[3]; // turn to angle var vecfrom[3]; // temp var vecto[3]; // temp var temp_loc[3]; // temp
sleep(1); // this fixes the problem where some players dont receive data proc_local(my, real_player_local); // function for client
my.enable_disconnect = on; my.event = real_player_event; // event function for server // server gives the players entity his player number gl_number_of_players += 1; // after entity created increment number of players ingame send_var(gl_number_of_players); // send this new number to all connected people
my._player_number = gl_number_of_players; // set the player number to the skill
// this code is to solve high latency creation problem sleep(.3); // this can be left at .3 no matter what ent_sendnow(my); sleep(.3); // sleep(3); // high latency solution for now set_nosend(); // dont send any automatic updates
my.enable_click = on; my.enable_touch = on; my.enable_release = on; my.passable = on;
// set faction and race for host if(connection == 3 && me == player) { my._faction = id_faction; my._race = id_race; }
// wait for faction and race if client while((!my._faction) && (!my._race)) {wait(1);} send_skill(my._faction, SEND_ALL);
if(my._race == RACE_HUMAN_MALE) { my._health = HEALTH_HUMAN; my._mana = MANA_HUMAN; my._movespeed = 8; }
if(my._race == RACE_ORC_MALE) { my._health = HEALTH_ORC; my._mana = MANA_ORC; my._movespeed = 13; }
// send entity skills of already ingame players you = ent_next(NULL); while(you != NULL) { if(you._movespeed) { send_skill(you._movespeed, SEND_ALL); // send the movespeed so others can move my entity } if(you._player_number) { send_skill(you._player_number, SEND_ALL); // send my player number skill to all players } if(you._faction) { send_skill(you._faction, SEND_ALL); // send my faction id skill to all players } if(you._health) { send_skill(you._health, SEND_ALL); // send health skill to all players } if(you._mana) { send_skill(you._mana, SEND_ALL); // send my mana skill to all players } if(you._race) { send_skill(you._race, SEND_ALL); // send my race skill to all players }
// send position and mode also for right start positions send_skill(you.x, SEND_ALL); send_skill(you.y, SEND_ALL); send_skill(you._mode, SEND_ALL);
you = ent_next(you); }
// set initial target coordinates so entity doesnt start moving to nullvector at start my._target_x = my.x; my._target_y = my.y;
// start animation loop for host animate(); my._mode = mode_stand; // starting mode
// intially set animation to stand, this will be changed below if necessary my._animation_state = animation_state_STAND;
// trace to geht right height to terrain vecFrom.x = my.x; vecFrom.y = my.y; vecFrom.z = 200; vec_set(vecTo,VecFrom); vecTo.z = -200; c_trace(vecFrom,vecTo, IGNORE_ME|IGNORE_PASSABLE|IGNORE_CONTENT|IGNORE_SPRITES|USE_POLYGON|GLIDE); vec_set(temp_loc,vecTo); my.z = target.z + 32;
my.invisible = off; while(1) {
my._prev_anim_state = my._animation_state; // save animation state //stand if(my._mode == mode_stand) { my._animation_state = animation_state_STAND; my._receive_z = 0; if(abs(my.x - my._target_x) + abs(my.y - my._target_y) > 20) { my._mode = mode_walk; } } //move to location stored in my.target_x if(my._mode == mode_walk) { my._animation_state = animation_state_WALK; my.tilt = 0; // we only need the correct "pan" angle, not tilt
while (abs(my.x - my._target_x) + abs(my.y - my._target_y) > 15 ) // stop near the target { // turn vec_set(temp.x, my._target_x); vec_sub(temp.x, my.x); vec_to_angle(dire.pan, temp.x); if((my.pan < dire.pan + 10) || (my.pan > dire.pan - 10) ) { my.pan = my.pan + ang(dire.pan-my.pan) * 0.5 * time;//i am turning }
// move vec_diff(temp.x, my.x, my._target_x); vec_normalize(temp.x,- my._movespeed * time); // ent_move cause its 20x faster move_mode = glide+ignore_me+ignore_you+ignore_sprites+ignore_passable; ent_move(nullvector, temp.x); //c_move(my, nullvector, temp.x, glide|ignore_me|ignore_you|IGNORE_CONTENT|ignore_sprites|ignore_passable ); // trace to geht right height to terrain vecFrom.x = my.x; vecFrom.y = my.y; vecFrom.z = 200; vec_set(vecTo,VecFrom); vecTo.z = -200; c_trace(vecFrom,vecTo, IGNORE_ME|IGNORE_PASSABLE|IGNORE_CONTENT|IGNORE_SPRITES|USE_POLYGON|GLIDE); vec_set(temp_loc,vecTo); my.z = target.z + 32; // send current position to other clients vec_set(my._receive_x, my.x); my._receive_z = 0; send_skill(my._receive_x, SEND_ALL | SEND_VEC | SEND_RATE | SEND_UNRELIABLE);
wait (1); }
// switch back to standing my._mode = mode_stand; vec_set(my._receive_x, my.x); // z = 1 indicates stop of movement my._receive_z = 1; send_skill(my._receive_z, SEND_ALL | SEND_VEC ); wait(1); } wait (1); }
}
// function create_player() create a player at spawnpoint function create_player() { if (id_race == RACE_HUMAN_MALE ) { player = ent_create(str_cbabe,vector(100,300,0),real_player); while(!player) {wait(1);} } if (id_race == RACE_ORC_MALE ) { player = ent_create(str_warlock,vector(100,100,0),real_player); while(!player) {wait(1);} }
}
// set the race to human function set_race_human() { id_race = RACE_HUMAN_MALE; cl_race_not_set = FALSE; }
// set the race to orc function set_race_orc() { id_race = RACE_ORC_MALE; cl_race_not_set = FALSE; }
// set the race to woodelv function set_race_woodelv() { id_race = RACE_HUMAN_MALE; cl_race_not_set = FALSE; }
Last edited by ulf; 05/29/06 12:08.
|
|
|
Re: multiplayer game development blog
[Re: ulf]
#72172
05/30/06 04:50
05/30/06 04:50
|
Joined: Aug 2001
Posts: 2,320 Alberta, Canada
William
Expert
|
Expert
Joined: Aug 2001
Posts: 2,320
Alberta, Canada
|
Your code looks good Ulf. Another tip might be to create your multiplayer entities at a position like vec(2000,2000,-5000), and then afterwards when your ready, set them to their proper positions. I noticed that upon creating some entities, you'll bounce into them even if you set them as passable, due to the time needed for it's instances, ect. Anyways, i've finished converting a unique weapon compared to what I was converting before. I've learned a few things, and have an update to the interpolation code with extrapolation. Code:
function movement_smooth() //collision box { my.invisible = on; my.passable = on; var rotate[3]; var s_angle[3]; var old_angle[3]; var rpos[3]; var zpos[3]; var pos_add; var calc_angle[3]; var move[3]; var move_old[3]; var zspeed; var length;
while(1) { if(my != decoy_1)&&(!lan) { my.passable = on;
vec_set(s_angle.pan, my.server_angle); //iterpolate server angle rotate.pan = ang(s_angle.pan - my.pan); my.pan += rotate.pan * 0.8 * time; rotate.tilt = ang(s_angle.tilt - my.tilt); my.tilt += rotate.tilt * 0.8 * time; rotate.roll = ang(s_angle.roll - my.roll); my.roll += rotate.roll * 0.8 * time; vec_set(old_angle.pan, my.server_angle);
vec_set(move.x, my.server_origin); //interpolate server position if(move_old.x != move.x){vec_set(length.x, move.x); vec_sub(length.x, rpos.x); vec_set(temp.x, move.x); vec_sub(temp.x, rpos.x); vec_to_angle(calc_angle.pan, temp.x);} rpos.x += length.x * 0.6 * time; rpos.y += length.y * 0.6 * time; rpos.z += length.z * 0.6 * time;
vec_set(zpos.x, rpos.x);
pos_add = my.fspeed * 4; //extrapolate to original position, fspeed is the speed of entity if(pos_add > 400){pos_add = 400;} //if(pos_add < 40){pos_add = 0;} temp.x = -pos_add; temp.y = 0; temp.z = 0; vec_rotate(temp.x, my.pan); vec_add(zpos.x, temp.x); vec_set(my.x, zpos.x); vec_set(zpos.x, nullvector);
if(key_v){vec_set(my.x, my.server_origin);} if(my.fspeed < 5){vec_set(my.x, my.server_origin);}
vec_set(temp.x, nullvector); if(my.server_origin != move_old.x){my.fspeed = vec_dist(my.server_origin, move_old.x); my.fspeed /= 2.74; zspeed = move.z - move_old.z; zspeed /= 2.74;} if(my.tracekart){you = ptr_for_handle(my.tracekart); vec_set(you.x, my.x); if(you.spin == 0){vec_set(you.pan, my.pan);} you.z -= 27;} vec_set(move_old.x, my.server_origin); } wait(1); } } This actually worked quite well, and now all the karts on each client and server see eachother at their proper positions with smoothness. But, there is one thing I should say. This will most likely only work in a situation where you have acceleration, such as a racing game. Because you extrapolate according to your acceleration. If you put this in a FPS game, the extrapolation would be sudden, and it would look jarred. It needs a certain degree of predictability too, since it extrapolates according to the players angle. If your angle updates are very far behind, or your angle quickly changes, this may not work as well. But, this is a good solution for racing games, and should fix the problem of 2 players crossing the finish line at same time, and on one computer it shows you win, but on another you lose, and in the end, you win, because on the other computer your position is being interpolated. It also fixs the problems with aiming projectiles. If your doing an FPS you wont have these problems anyways. Afterall, a character in an FPS moves much slower... A few other things i've learned: - If your rotating bones via user input, just send the position similar to sending the server angle. - Local entities can be used in a dynamic multipalyer environment. I ran into a problem where I needed to create a couple entities everysecond(projectiles). At first I coded it using multiplayer entities(instances), but then I realized the amount of traffic this produced. So I ended up just sending a skill determining if the entities should be fired, and if they did, fire local entities on each computer. Do the events on only one computer, and have local entities for the rest. Of course the problem with this is that the two local entities can become out of sync(delete on one comp, stays on another). So only use this method if you really need it, and if your sure there movement is quite linear... so they'll generally delete at the same time on both comps, ect.
|
|
|
Re: multiplayer game development blog
[Re: ulf]
#72174
05/30/06 07:30
05/30/06 07:30
|
Joined: Aug 2001
Posts: 2,320 Alberta, Canada
William
Expert
|
Expert
Joined: Aug 2001
Posts: 2,320
Alberta, Canada
|
Quote:
good to see you advancing with your racing game. when will you release more info or even a demo?
I've gotta set up a website first, and decided there is no point doing so until the game is closer to completion... Probably will see something(info, screens, video) sometime in the next 6 months. But nothing is certain... so it may take longer.
|
|
|
Re: multiplayer game development blog
[Re: William]
#72175
05/30/06 09:59
05/30/06 09:59
|
Joined: Aug 2005
Posts: 1,012 germany, dresden
ulf
OP
Serious User
|
OP
Serious User
Joined: Aug 2005
Posts: 1,012
germany, dresden
|
30.05.06just did a load test. what i did was place 63 spiders in the level with a simple script from the aum. then i send all positions of them with: vec_set(my._receive_x, my.x); my._receive_z = 0; send_skill(my._receive_x, SEND_ALL | SEND_VEC | SEND_RATE | SEND_UNRELIABLE); and move them on the client with this data. dplay_entrate was set to 4. this is what i get with 2 clients connected. as you can see the kiloByte per second is at around ~10! when only 1 client was connected it was at ~5 wich makes sense. no my dsl has only 25kb upload and my target is to get 20kb at average during gameplay maximum. so dsl players can host a game. the problem is in the final game there should be 10 clients. so i would end up haveing 50kb upload with the current code. i probably have to lower the dplay_entrate and see how this works. shure 60 entities moving at once is worst case but can and will happen in the game. maybe some of you found this interesting or have suggestions how to lower the send load. this is also why i requested send_skill_to. as players only need to receive the data of the entities they currently see this would in my case mean every player only needs the data of around 30 entities at once thus cutting the upload bandwith in half! in warcraft 3 with around 80-100 moving entities at once in a game i get like 5kb-7kb average upload as host... i wonder how they do it. i guess they use this system so they only send movement data to the clients who really need it and not to all. like we currently only can do with send_skill ):
|
|
|
Re: multiplayer game development blog
[Re: ulf]
#72176
05/31/06 00:51
05/31/06 00:51
|
Joined: Aug 2001
Posts: 2,320 Alberta, Canada
William
Expert
|
Expert
Joined: Aug 2001
Posts: 2,320
Alberta, Canada
|
I would think in an RTS game, you'd only send the mouseclick position, and let the local pathfinding do the rest. As to the data for each little entity(health, ammo, ect.), sending to comps that only need it would indeed help. But mabye you can find a way to detract health and the such locally, and still keep it all in snyc. After all, when you delete an entity, it will delete on all comps at the same time. Just make sure when their attacking eachother you display the effects, and mabye a mock up health bar(while only doing the real health work on one comp).
|
|
|
|