Getting Online with Multiplayer Gaming(14)

Getting Online with Multiplayer Gaming(14)

 

Updating Players

In order to synchronize itself with clients, the server needs to maintain a simplified
version of the game running internally. This version of the game doesn’t include
graphics, sound, or any other fancy features; it only needs to track player’s actions.

The server tracks those actions by updating the player’s actions every 33ms (just as
the client application will do). Those actions include walking and waiting for other
specific states to clear (such as sword swinging and being hurt).

The cApp::update_players function is responsible for updating all players:

void  cApp::update_players()
{
    sStateChangeMsg change_msg;

    
// loop through all players
     for ( int  i = 0; i < MAX_PLAYERS; i++)
    {
        
// only update connected players
         if (! m_players[i].connected)
            
continue ;

        
long  elapsed = timeGetTime() - m_players[i].last_update_time;

        
if (m_players[i].last_state == STATE_MOVE)
        {
            
// calculate amount of movement by time passed
             float  speed  = elapsed / 1000.0f * m_players[i].speed;
            
float  x_move = sin(m_players[i].direction) * speed;
            
float  z_move = cos(m_players[i].direction) * speed;

            
// check for movement collision - can not walk past anything blocking path
             if (check_intersect(&m_level_mesh,
                m_players[i].x_pos,          m_players[i].y_pos + 16.0f, m_players[i].z_pos,
                m_players[i].x_pos + x_move, m_players[i].y_pos + 16.0f, m_players[i].z_pos + z_move))
            {
                x_move = z_move = 0.0f;
            }

            
// update player coordinates
            m_players[i].x_pos += x_move;
            m_players[i].y_pos  = 0.0f;     
// stay on ground
            m_players[i].z_pos += z_move;

            m_players[i].last_update_time = timeGetTime();
        }
        
else   if (m_players[i].last_state == STATE_SWING && elapsed > 1000)    // clear swing status after 1 second
        {
            m_players[i].last_state = STATE_IDLE;

            
// send network message to player to clear
            change_msg.header.type      = MSG_STATE_CHANGE;
            change_msg.header.size      = 
sizeof (sStateChangeMsg);
            change_msg.header.player_id = m_players[i].player_id;
            change_msg.x_pos            = m_players[i].x_pos;
            change_msg.y_pos            = m_players[i].y_pos;
            change_msg.z_pos            = m_players[i].z_pos;
            change_msg.direction        = m_players[i].direction;
            change_msg.speed            = m_players[i].speed;
            change_msg.state            = m_players[i].last_state;

            send_network_msg(&change_msg, DPNSEND_NOLOOPBACK, ALL_CLIENT_PLAYERS);
        }
        
else   if (m_players[i].last_state == STATE_HURT && elapsed > 1000)     // clear hurt status after 1 second
        {
            m_players[i].last_state = STATE_IDLE;

            
// send network message to player to clear
            change_msg.header.type      = MSG_STATE_CHANGE;
            change_msg.header.size      = 
sizeof (sStateChangeMsg);
            change_msg.header.player_id = m_players[i].player_id;
            change_msg.x_pos            = m_players[i].x_pos;
            change_msg.y_pos            = m_players[i].y_pos;
            change_msg.z_pos            = m_players[i].z_pos;
            change_msg.direction        = m_players[i].direction;
            change_msg.speed            = m_players[i].speed;
            change_msg.state            = m_players[i].last_state;

            send_network_msg(&change_msg, DPNSEND_NOLOOPBACK, ALL_CLIENT_PLAYERS);
        }
    }
}

As the server scans the list of players, it determines which players are connected
and calculates the time since the last server update for all connected players. Next,
if a player’s state is set to STATE_MOVE, the elapsed time is used to move the player.

Next, the server deals with the STATE_SWING and STATE_HURT states. Those states are
cleared only after one second has passed (as determined by the elapsed time).

Surprisingly, that’s it for cApp::update_players! Remember that the update_players function
is called every 33ms, so keeping the function quick and to the point is crucial.
Once all players are updated, you need to notify other players.

 

Updating the Network Clients

Throughout earlier sections in this chapter, I mentioned periodic server updates
that are sent to the client in order to synchronize game-play. That’s the purpose of
the cApp::update_network function. The update_network function is quick and to the
point, sending out the current state of all connected clients every 100ms.

void  cApp::update_network()
{
    
// send all player updates
     for ( long  i = 0; i < MAX_PLAYERS; i++)
    {
        
if (m_players[i].connected)   // only send data about connected players
        {
            sStateChangeMsg change_msg;

            change_msg.header.type      = MSG_STATE_CHANGE;
            change_msg.header.size      = 
sizeof (sStateChangeMsg);
            change_msg.header.player_id = m_players[i].player_id;
            change_msg.x_pos            = m_players[i].x_pos;
            change_msg.y_pos            = m_players[i].y_pos;
            change_msg.z_pos            = m_players[i].z_pos;
            change_msg.direction        = m_players[i].direction;
            change_msg.speed            = m_players[i].speed;
            change_msg.state            = m_players[i].last_state;
            change_msg.latency          = m_players[i].latency;

            send_network_msg(&change_msg, DPNSEND_NOLOOPBACK, ALL_CLIENT_PLAYERS);
        }
    }
}

 

Calculating Latency

The server periodically calculates the time it takes a message to be received from a
client and uses the latency in the timed calculations to update clients, all of which
is crucial to maintaining the synchronization of the game. The function that calculates
the latency is update_latency, and it is called every 10 seconds from the main
application loop (cApp::frame).

void  cApp::update_latency()
{
    
// go through all players
     for ( long  i = 0; i < MAX_PLAYERS; i++)
    {
        
if (m_players[i].connected)   // only process connected players
        {
            DPN_CONNECTION_INFO connect_info;

            
// request player connection settings
             if (SUCCEEDED(m_server.get_server()->GetConnectionInfo(m_players[i].player_id, &connect_info, 0)))
            {
                m_players[i].latency = connect_info.dwRoundTripLatencyMS / 2;

                
// bounds latency to 1 second
                 if (m_players[i].latency > 1000)
                    m_players[i].latency = 1000;
            }
            
else
                m_players[i].latency = 0;
        }
    }
}

To calculate the latency, the server queries DirectPlay for the connection statistics
via the IDirectPlay8Server::GetConnectInfo function. That function call takes a structure
(DPN_CONNECTION_INFO) as an argument, and inside the structure is a variable that
represents the roundtrip latency time in milliseconds. The server divides that
latency value in half and stores it in each player’s data structure.

 

Other function:

int  WINAPI WinMain(HINSTANCE inst, HINSTANCE, LPSTR cmd_line,  int  cmd_show)
{
    
const   char * class_name = "ServerClass";

    WNDCLASSEX win_class;

    
// create window class and register it

    win_class.cbSize        = 
sizeof (win_class);
    win_class.style         = CS_CLASSDC;
    win_class.lpfnWndProc   = window_proc;
    win_class.cbClsExtra    = 0;
    win_class.cbWndExtra    = 0;
    win_class.hInstance     = inst;
    win_class.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
    win_class.hCursor       = LoadCursor(NULL, IDC_ARROW);
    win_class.hbrBackground = (HBRUSH) GetStockObject(LTGRAY_BRUSH);;
    win_class.lpszMenuName  = NULL;
    win_class.lpszClassName = class_name;
    win_class.hIconSm       = LoadIcon(NULL, IDI_APPLICATION);

    
if (! RegisterClassEx(&win_class))
        
return   false ;

    DWORD style = WS_BORDER | WS_CAPTION | WS_MINIMIZEBOX | WS_SYSMENU;

    DWORD pos_x = (get_screen_width()  - CLIENT_WIDTH) / 2;
    DWORD pos_y = (get_screen_height() - CLIENT_HEIGHT) / 4;
    
    g_hwnd = CreateWindow(class_name, "Network Server Demo", style, pos_x, pos_y, 
                          CLIENT_WIDTH, CLIENT_HEIGHT, NULL, NULL, inst, NULL);

    
if (g_hwnd == NULL)
        
return  -1;

    ShowWindow(g_hwnd, SW_NORMAL);
    UpdateWindow(g_hwnd);

    resize_window(g_hwnd, CLIENT_WIDTH, CLIENT_HEIGHT);

    cApp app;
    app.run();

    
return  0;
}

///////////////////////////////////////////////////////////////////////////////////////

BOOL CALLBACK config_dlg_proc(HWND dlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
    
static  HWND adapter_wnd;

    
switch (msg)
    {
    
case  WM_INITDIALOG:
        adapter_wnd = GetDlgItem(dlg, IDC_ADAPTERS);

        
// add adapter names to list
         for ( long  i = 0; i < g_adapter->get_num_adapters(); i++)
        {
            
char  text[256];
            g_adapter->get_name(i, text);

            insert_string_to_combo(adapter_wnd, i, text);
        }

        
// select first adapter in list
        set_combo_cur_sel(adapter_wnd, 0);

        
return  TRUE;

    
case  WM_COMMAND:
        
switch (LOWORD(wParam))
        {
        
case  IDC_OK:
            {
            
int  sel = ( int ) get_combo_cur_sel(adapter_wnd);  

            
// make sure an adapter was selected
             if (sel == LB_ERR)
                
break ;

            g_app->set_adapter_guid(g_adapter->get_guid(sel));

            EndDialog(dlg, TRUE);
            
return  TRUE;
            }

        
case  IDC_CANCEL:
            EndDialog(dlg, FALSE);
            
return  TRUE;
        }
    }

    
return  FALSE;
}

///////////////////////////////////////////////////////////////////////////////////////

bool  cApp::init()
{
    
// select a network adapter to use (or quit if selected)
     if (! select_adapter())
        
return   false ;

    setup_app_window();

    
// intialize the game, set display mode, load level mesh.
     if (! init_game())
    {
        show_error_msg(
false , "Unable to initialize game.");
        
return   false ;
    }

    
// begin hosting the game session
     if (! host_game())
    {
        show_error_msg(
false , "Unable to host network server.");
        
return   false ;
    }

    
return   true ;
}

///////////////////////////////////////////////////////////////////////////////////////

bool  cApp::frame()
{
    
static  DWORD player_counter  = timeGetTime();
    
static  DWORD network_counter = timeGetTime();
    
static  DWORD latency_counter = timeGetTime();

    process_queue_msg();

    
// update players every 33ms (30 times a second)
     if (timeGetTime() > player_counter + 33)
    {
        update_players();
        player_counter = timeGetTime();
    }
    
    
// send out network update every 100ms (10 times a second)
     if (timeGetTime() > network_counter + 100)
    {
        update_network();
        network_counter = timeGetTime();
    }

    
// update player latency values every 10 seconds
     if (timeGetTime() > latency_counter + 10000)
    {
        update_latency();
        latency_counter = timeGetTime();
    }

    
return   true ;
}

///////////////////////////////////////////////////////////////////////////////////////

void  cApp::shutdown()
{
    delete[] m_msgs;
    m_msgs = NULL;

    delete[] m_players;
    m_players = NULL;

    DeleteCriticalSection(&m_msg_cs);
}
    
///////////////////////////////////////////////////////////////////////////////////////

bool  cApp::select_adapter()
{
    ShowWindow(g_hwnd, SW_HIDE);    
// hide main window
    m_adapter.init();

    
return  ( bool ) DialogBox(get_window_inst(), MAKEINTRESOURCE(IDD_CONFIG), g_hwnd, config_dlg_proc);
}

///////////////////////////////////////////////////////////////////////////////////////

void  cApp::setup_app_window()
{
    
// window to hold host IP address
    m_controls[CONTROL_SERVER_IP] = CreateWindow("STATIC", "", WS_CHILD | WS_VISIBLE, 4, 4, 312, 18,
                                                 g_hwnd, NULL, get_window_inst(), NULL);

    
// window to hold number of connected players
    m_controls[CONTROL_PLAYER_NUM] = CreateWindow("STATIC", "No Connected Players",  WS_CHILD | WS_VISIBLE,
                                                  4, 26, 312, 18, g_hwnd, NULL, get_window_inst(), NULL);

    
// list box to display connect player's names
    m_controls[CONTROL_PLAYER_LIST] = CreateWindow("LISTBOX", "", WS_CHILD | WS_BORDER | WS_VSCROLL | WS_VISIBLE,
                                                   4, 48, 312, 154, g_hwnd, NULL, get_window_inst(), NULL);

    
// show main window
    ShowWindow(g_hwnd, SW_SHOW);
}

///////////////////////////////////////////////////////////////////////////////////////

bool  cApp::init_game()
{
    create_display(g_hwnd, CLIENT_WIDTH, CLIENT_HEIGHT, 16, 
true true );

    
// load the level mesh for collision checking
     if (! m_level_mesh.load("..\\Data\\ArenaCM.x", "..\\Data\\"))
        
return   false ;

    m_msgs = 
new  sMsg[MAX_MESSAGES];
    m_msg_head = m_msg_tail = 0;

    m_players = 
new  sPlayer[MAX_PLAYERS];
    m_connected_player_num = 0;

    
return   true ;
}

///////////////////////////////////////////////////////////////////////////////////////

bool  cApp::host_game()
{
    
// configure server and begin hosting

    m_server.init();

    
if (! m_server.host(m_adapter_guid, 9123, "RPGGAME", NULL, MAX_PLAYERS))
        
return   false ;

    
char  text[33], ip[16];

    
// get server ip address and display in application window
    m_server.get_ip(ip, 0);
    sprintf(text, "Host IP Address: %s", ip);
    SetWindowText(m_controls[CONTROL_SERVER_IP], text);

    
return   true ;
}

download source and solution

你可能感兴趣的:(Getting Online with Multiplayer Gaming(14))