GAME MAKER 강좌
KAYAN
◈ 서버와의 동기화
게임 실행 오브젝트(obj_game_stage)의 [Room Start 이벤트]를 추가하고, 클라이언트가 서버의 동기화 요청을 합니다.
★ obj_game_stage 오브젝트- Room Start 이벤트 ★ //클라이언트가 룸에 입장했을 때 서버와의 동기화 요청 if global.select_server == 0{ //---------- 룸 오브젝트 갱신 buffer_seek( global.net_buffer, buffer_seek_start, 0 ); buffer_write( global.net_buffer , buffer_u16, 2 ); //패킷 이벤트 번호 network_send_packet( global.client_socket, global.net_buffer, buffer_tell( global.net_buffer ) ); //---------- 룸 오브젝트 갱신 global.game_start_check = 0; //서버 접속시 게임 중인지 체크 //---------- 서버와의 동기화 요청( obj_game_user:room_sync ) buffer_seek( global.net_buffer, buffer_seek_start, 0 ); buffer_write( global.net_buffer , buffer_u16, 3 ); //패킷 이벤트 번호 buffer_write( global.net_buffer , buffer_bool, 1 ); network_send_packet( global.client_socket, global.net_buffer, buffer_tell( global.net_buffer ) ); //---------- 서버와의 동기화 요청( obj_game_user:room_sync ) } |
2번 패킷은 게임 중에 룸에 들어갔을 때, 게임룸에 변경된 맵 정보를 서버와 동기화하도록 요청합니다.
3번 패킷은 클라이언트가 게임룸에 입장했다고, 서버에 알립니다.
이것은 클라이언트가 게임룸에 들어 갔을 때, 게임 플레이를 하기 위해 서버에 요청사항을 보내는 것입니다.
그리고 서버 오브젝트의 네트워크 비동기 이벤트에서 클라이언트의 요청을 처리하고, 현재 서버의 맵에 있는 정보를 클라이언트에게 보냅니다.
클라이언트는 서버가 보낸 맵 정보로 게임룸을 업데이트하게 됩니다.
(▲ 맵 업데이트 - 기존에 있던 유저와 신규 클라이언트 유저 생성)
이와 같은 방식으로 구성할 것입니다.
먼저 맵의 동기화 입니다.
플레이어는 게임 실행 오브젝트(obj_game_stage)에서 자동으로 생성해주므로, 룸에 있는 기존의 플레이어 캐릭터만 생성하도록 합니다.
패킷에 담을 값은 오브젝트를 동기화를 위해 새로 생성할 때 필요한 최소값만을 보냅니다.
★ obj_server_system 오브젝트 - Async - Networking 이벤트 ★ //---------- 클라이언트가 룸에 입장했을 때 서버와의 동기화 case 2: var _check = buffer_read( t_buffer, buffer_bool ); //서버 접속시 게임 중인지 체크 //플레이어 생성 with( obj_player_parent ){ //현재 유저는 제외하고, 기존 룸에 있는 플레이어 캐릭터만 갱신하도록 함 if !( user_id == sock ){ buffer_seek( global.net_buffer, buffer_seek_start, 0 ); buffer_write( global.net_buffer , buffer_u16, 20 ); //패킷 이벤트 번호 buffer_write( global.net_buffer , buffer_u16, object_map_id ); buffer_write( global.net_buffer , buffer_f16, x ); buffer_write( global.net_buffer , buffer_f16, y ); buffer_write( global.net_buffer , buffer_f16, depth ); buffer_write( global.net_buffer , buffer_string, object_get_name( object_index ) ); buffer_write( global.net_buffer , buffer_u8, user_id ); buffer_write( global.net_buffer , buffer_string, user_name ); network_send_packet( sock, global.net_buffer, buffer_tell( global.net_buffer ) ); } } // 총알 생성 with( obj_bullet ){ buffer_seek( global.net_buffer, buffer_seek_start, 0 ); buffer_write( global.net_buffer , buffer_u16, 27 ); //패킷 이벤트 번호 buffer_write( global.net_buffer , buffer_u16, 0 ); buffer_write( global.net_buffer , buffer_f16, x ); buffer_write( global.net_buffer , buffer_f16, y ); buffer_write( global.net_buffer , buffer_f16, direction ); buffer_write( global.net_buffer , buffer_f16, alarm[0] ); //파기시간 buffer_write( global.net_buffer , buffer_string, object_get_name( object_index ) ); network_send_packet( sock, global.net_buffer, buffer_tell( global.net_buffer ) ); } break; //---------- 클라이언트가 룸에 입장했을 때 서버와의 동기화 |
그리고 총알과 같이 갱신해야 하는 오브젝트가 있다면 클라이언트에게 생성하라고 지시합니다.
클라이언트 오브젝트로 돌아가 [네트워크 비동기 이벤트]에 서버에서 동기화하라고 보낸 패킷을 정리합니다.
먼저 플레이어 생성입니다.
20 번 수신 이벤트에 버퍼의 순서대로 값을 읽어 적용합니다.
★ obj_client_system 오브젝트 - Async - Networking 이벤트 ★ //---------- 플레이어 생성 case 20: var _map_id, _x, _y, _depth, _obj, inst; _map_id = buffer_read( t_buffer, buffer_u16 ); _x = buffer_read( t_buffer, buffer_f16 ); _y = buffer_read( t_buffer, buffer_f16 ); _depth = buffer_read( t_buffer, buffer_f16 ); _obj = asset_get_index( buffer_read( t_buffer, buffer_string ) ); if ( object_exists( _obj ) ){ inst = instance_create_depth( _x, _y, _depth, _obj ); inst.user_id = buffer_read( t_buffer, buffer_u8 ); inst.user_name = buffer_read( t_buffer, buffer_string ); if ( inst.user_id == global.user_id ){ inst.select = 1; }else{ inst.select = 0; } } break; //---------- 플레이어 생성 |
다음은 총알 동기화입니다.
27 번 수신 이벤트에 버퍼 순서대로 값을 읽어 적용합니다.
★ obj_client_system 오브젝트 - Async - Networking 이벤트 ★ //---------- 총알 생성 case 27: var _user_id, _x, _y, _obj, _dir, inst, _data; _user_id = buffer_read( t_buffer, buffer_u8 ); _x = buffer_read( t_buffer, buffer_f16 ); _y = buffer_read( t_buffer, buffer_f16 ); _dir = buffer_read( t_buffer, buffer_f16 ); _data = buffer_read( t_buffer, buffer_f16 ); //alarm0 _obj = asset_get_index( buffer_read( t_buffer, buffer_string ) ); if ( object_exists( _obj ) ){ inst = instance_create_depth( _x, _y, depth, _obj ); inst.direction = _dir; inst.user_id = _user_id; inst.alarm[0] = _data; } break; //---------- 총알 생성 |
◈ 플레이어의 네트워크 동기화
다음은 플레이어의 이동, 체력과 같은 정보를 다른 클라이언트와 동기화해야 합니다.
먼저, 플레이어의 이동을 동기화 합니다.
플레이어 오브젝트(obj_player_1)[Alarm 5 이벤트]를 추가합니다.([Create 이벤트]에 [Alarm 5 이벤트]를 활성화 시켜주었는지 확인해봅시다)
이 이벤트는 매회 실행해서 플레이어의 정보를 다른 클라이언트와 공유되어야 합니다.
★ obj_player_1 오브젝트 - Alarm 5 이벤트 ★ alarm[5] = 1; //-------------------- 플레이어 위치 갱신 if ( select == 1 ) && ( HP>0 ){ //---------- 클라이언트 if ( global.select_server == 0 ){ buffer_seek( global.net_buffer, buffer_seek_start, 0 ); buffer_write( global.net_buffer , buffer_u16, 15 ); //패킷 이벤트 번호 buffer_write( global.net_buffer , buffer_u8, user_id ); buffer_write( global.net_buffer , buffer_f16, x ); //x buffer_write( global.net_buffer , buffer_f16, y ); //y buffer_write( global.net_buffer , buffer_f16, direction ); //direction network_send_packet( global.client_socket, global.net_buffer, buffer_tell( global.net_buffer ) ); } //---------- 클라이언트 } |
클라이언트 유저의 플레이어 캐릭터 위치를 서버에 보냅니다.
그러면 서버에서는 이것을 게임룸에 있는 플레이어 캐릭터의 위치를 업데이트 합니다.
★ obj_server_system 오브젝트 - Async - Networking 이벤트 ★ case 15://update client ( x, y ) position var _x, _y, _dir, _user_id, _HP; _user_id = buffer_read( t_buffer, buffer_u8 ); //id _x = buffer_read( t_buffer, buffer_f16 ); _y = buffer_read( t_buffer, buffer_f16 ); _dir = buffer_read( t_buffer, buffer_f16 ); with( obj_player_parent ){ if ( user_id == _user_id ){ //해당 유저의 위치를 업데이트 move_x = _x; move_y = _y; direction = _dir; break; }} //클라이언트에 패킷을 보내 적용 buffer_seek( global.net_buffer, buffer_seek_start, 0 ); buffer_write( global.net_buffer , buffer_u16, 15 ); //패킷 이벤트 번호 buffer_write( global.net_buffer , buffer_u8, _user_id ); //socket buffer_write( global.net_buffer , buffer_f16, _x ); //x buffer_write( global.net_buffer , buffer_f16, _y ); //y buffer_write( global.net_buffer , buffer_f16, _dir ); //direction for ( var i = 0; i<ds_list_size( global.socketlist ); i+ = 1; ){ if !( ds_list_find_value( global.socketlist, i ) == sock ){ network_send_packet( ds_list_find_value( global.socketlist, i ), global.net_buffer, buffer_tell( global.net_buffer ) ); }} break; |
그리고 해당 플레이어 캐릭터 위치를 다시 다른 클라이언트 유저에게도 보냅니다.
다시 클라이언트 오브젝트로 돌아와서 서버에서 보내온 위치를 업데이트 합니다.
★ obj_client_system 오브젝트 - Async - Networking 이벤트 ★ case 15: var _user_id, _x, _y, _dir; _user_id = buffer_read( t_buffer, buffer_u8 ); //user_id 0 = server 1 ~ = client _x = buffer_read( t_buffer, buffer_f16 ); _y = buffer_read( t_buffer, buffer_f16 ); _dir = buffer_read( t_buffer, buffer_f16 ); with( obj_player_parent ){ if ( _user_id == user_id ){ move_x = _x; move_y = _y; direction = _dir; break; } } break; |
클라이언트의 플레이어 위치는 업데이트 했고, 서버 유저의 플레이어 위치를 업데이트 해주어야 합니다.
플레이어 오브젝트의 [Alarm 5 이벤트]에 플레이어 위치를 다른 클라이언트에게 보내줍니다.
서버의 [네트워크 비동기 이벤트]에서 보낸 정보와 동일하게 모든 클라이언트에게 전달하면 됩니다.
★ obj_player_1 오브젝트 - Alarm 5 이벤트 ★ alarm[5] = 1; //-------------------- 플레이어 위치 갱신 ------------------- if ( select == 1 ) && ( HP>0 ){ //---------- 클라이언트 if ( global.select_server == 0 ){ buffer_seek( global.net_buffer, buffer_seek_start, 0 ); buffer_write( global.net_buffer , buffer_u16, 15 ); //패킷 이벤트 번호 buffer_write( global.net_buffer , buffer_u8, user_id ); buffer_write( global.net_buffer , buffer_f16, x ); //x buffer_write( global.net_buffer , buffer_f16, y ); //y buffer_write( global.net_buffer , buffer_f16, direction ); //direction network_send_packet( global.client_socket, global.net_buffer, buffer_tell( global.net_buffer ) ); } //---------- 클라이언트 //---------- ▼ 서버 else{ buffer_seek( global.net_buffer, buffer_seek_start, 0 ); buffer_write( global.net_buffer , buffer_u16, 15 ); //패킷 이벤트 번호 buffer_write( global.net_buffer , buffer_u8, user_id ); buffer_write( global.net_buffer , buffer_f16, x ); //x buffer_write( global.net_buffer , buffer_f16, y ); //y buffer_write( global.net_buffer , buffer_f16, direction ); //direction for ( var i = 0; i<ds_list_size( global.socketlist ); i++; ){ network_send_packet( ds_list_find_value( global.socketlist, i ), global.net_buffer, buffer_tell( global.net_buffer ) ); } } //---------- ▲ 서버 } //-------------------- 플레이어 위치 갱신 ------------------- |
그러면 클라이언트는 서버의 플레이어 캐릭터도 업데이트하게 됩니다.
◈ 플레이어의 체력 업데이트
다음은 체력 정보를 업데이트 해야겠죠.
특수한 상황이 아니면 피격 판정이나 체력은 서버가 관리하는 것이 좋습니다.
플레이어 오브젝트의 [Alarm 5 이벤트]에서 HP의 정보를 모든 클라이언트에게 보냅니다.
★ obj_player_1 오브젝트 - Alarm 5 이벤트 ★ //-------------------- 플레이어 체력 갱신 ( 서버 ) if ( global.select_server == 1 ){ if ( HP>0 ){ buffer_seek( global.net_buffer, buffer_seek_start, 0 ); buffer_write( global.net_buffer , buffer_u16, 16 ); //패킷 이벤트 번호 buffer_write( global.net_buffer , buffer_u8, user_id ); buffer_write( global.net_buffer , buffer_f16, HP ); for ( var i = 0; i<ds_list_size( global.socketlist ); i++; ){ network_send_packet( ds_list_find_value( global.socketlist, i ), global.net_buffer, buffer_tell( global.net_buffer ) ); } } } //-------------------- 플레이어 체력 갱신 ( 서버 ) |
클라이언트 오브젝트에서 서버가 보내온 플레이어 캐릭터 체력 정보를 업데이트 합니다.
★ obj_client_system 오브젝트 - Async - Networking 이벤트 ★ case 16: var _user_id, _HP; _user_id = buffer_read( t_buffer, buffer_u8 ); //user_id 0 = server 1 ~ = client _HP = buffer_read( t_buffer, buffer_f16 ); //해당 유저의 체력을 업데이트 with( obj_player_parent ){ if ( _user_id == user_id ){ HP = _HP; break; }} break; |
플레이어 오브젝트의 [End Step 이벤트]를 추가하고 체력이 0이하면 플레이어를 파기하도록 합니다.
★ obj_player_1 오브젝트 - End Step 이벤트 if ( HP< = 0 ){ instance_destroy( ); } |
◈ 플레이어 이동 보간
플레이어의 위치좌표를 (x, y) 위치에 바로 적용하지 않은 것은, 네트워크 상의 딜레이를 고려해야 하기 때문입니다.
딜레이가 발생하면 플레이어의 캐릭터가 이동할 때, 끊김이 발생합니다.
(▲ 플레이어 이동 - 이동 보정)
(▲ 플레이어 이동 - 딜레이로 끊김)
이것을 완화하기 위해선 약간 보정이 필요합니다.
플레이어 오브젝트의 [End Step 이벤트]에 아래 내용을 추가합니다.
★ obj_player_1 오브젝트 - End Step 이벤트 ★ if !( global.user_id == user_id ){ x+ = ( move_x-x )*0.5; y+ = ( move_y-y )*0.5; if abs( move_x-x )<0.5{ x = move_x; } if abs( move_y-y )<0.5{ y = move_y; } } |
업데이트 이전의 (x, y)위치에서 새 위치(move_x, move_y)로 바로 이동하는 것이 아니라 단계별로 이동하게 됩니다.
'GameMaker강좌[GMS2] > 네트워크강좌' 카테고리의 다른 글
[게임메이커 강좌-네트워크][GMS2] 멀티플레이어 게임 만들기-7-접속자 목록 (0) | 2024.12.25 |
---|---|
[게임메이커 강좌-네트워크][GMS2] 멀티플레이어 게임 만들기-6-플레이어의 공격 업데이트 (0) | 2024.12.25 |
[게임메이커 강좌-네트워크][GMS2] 멀티플레이어 게임 만들기-4-클라이언트 (0) | 2024.12.25 |
[게임메이커 강좌-네트워크][GMS2] 멀티플레이어 게임 만들기-3-서버 게임 구성 (0) | 2024.12.25 |
[게임메이커 강좌-네트워크][GMS2] 멀티플레이어 게임 만들기-2-서버 만들기 (0) | 2024.12.25 |
댓글