Getting Online with Multiplayer Gaming(19)

Getting Online with Multiplayer Gaming(19)

 

Updating All Players

Whereas the local player’s input is processed in the cApp::frame function, the
update_players (which you saw in the code in the previous section) processes the
players according to their respective states.

Unlike the server's update_players function, the client's update_players function is simple.
The client is allowed to move players based only on their last known positions,
directions, and elapsed time since their last update.

Remember, only the server can clear the weapon-swinging and being-hurt states, so
the client has nothing to do at this point except update the various animations to
show the player what is going on:

void  cApp::update_players()
{
    
// process all active player movements
     for ( long  i = 0; i < MAX_PLAYERS; i++)
    {
        
if (! m_players[i].connected)
            
continue ;

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

        
if (m_players[i].last_state == STATE_MOVE)
        {
            
// process player movement state

            
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 collisions - can not walk past anything blocking path
             if (m_nodetree_mesh.is_ray_intersect(
                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,
                NULL))
            {
                x_move = z_move = 0.0f;
            }

            
// update coordinates

            EnterCriticalSection(&m_update_cs);

            m_players[i].x_pos += x_move;
            m_players[i].y_pos  = 0.0f;
            m_players[i].z_pos += z_move;

            m_players[i].last_update_time = timeGetTime();  
// reset time

            
if (m_players[i].last_anim != ANIM_WALK)
            {
                m_players[i].last_anim = ANIM_WALK;
                m_players[i].body.set_anim_set(&m_char_anim, "Walk", timeGetTime()/32);
            }

            LeaveCriticalSection(&m_update_cs);
        }
        
else   if (m_players[i].last_state == STATE_IDLE)
        {
            
// set new animations as needed

            
if (m_players[i].last_anim != ANIM_IDLE)
            {
                EnterCriticalSection(&m_update_cs);

                m_players[i].last_anim = ANIM_IDLE;
                m_players[i].body.set_anim_set(&m_char_anim, "Idle", timeGetTime()/32);

                LeaveCriticalSection(&m_update_cs);
            }
        }
        
else   if (m_players[i].last_state == STATE_SWING)
        {
            
if (m_players[i].last_anim != ANIM_SWING)
            {
                EnterCriticalSection(&m_update_cs);

                m_players[i].last_anim = ANIM_SWING;
                m_players[i].body.set_anim_set(&m_char_anim, "Swing", timeGetTime()/32);

                LeaveCriticalSection(&m_update_cs);
            }
        }
        
else   if (m_players[i].last_state == STATE_HURT)
        {
            
if (m_players[i].last_anim != ANIM_HURT)
            {
                EnterCriticalSection(&m_update_cs);

                m_players[i].last_anim = ANIM_HURT;
                m_players[i].body.set_anim_set(&m_char_anim, "Hurt", timeGetTime()/32);

                LeaveCriticalSection(&m_update_cs);
            }
        }
    }
}

Character animations are updated only if they differ from the last known animation.
The sPlayer::last_anim variable tracks the last known animation, although the
various ANIM_* macros define which animations to play.

 

Other Function:

int  WINAPI WinMain(HINSTANCE inst, HINSTANCE, LPSTR cmd_line,  int  cmd_show)
{        
    DWORD pos_x = (get_screen_width()  - CLIENT_WIDTH) / 2;
    DWORD pos_y = (get_screen_height() - CLIENT_HEIGHT) / 4;

    build_window(inst, "ClientClass", "Network Client Demo", 
                 WS_BORDER | WS_CAPTION | WS_MINIMIZEBOX | WS_SYSMENU,
                 pos_x, pos_y, CLIENT_WIDTH, CLIENT_HEIGHT);
    
    cApp app;
    app.run();

    
return  0;
}

/****************************************************************************************************/

cApp::cApp()
{
    
// clear class data           
    m_adapter_guid   = NULL;    
    m_host_ip[0]     = '\0';
    m_player_name[0] = '\0';    
    m_players        = NULL;
    m_num_players    = 0;   
    m_cam_angle      = 0.0f;
    m_font           = NULL;

    
// set global pointer
    g_app     =  this ;
    g_adapter = &m_adapter;

    InitializeCriticalSection(&m_update_cs);
}

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

bool  cApp::init()
{
    
if (! select_adapter())
        
return   false ;

    
if (! init_game())
    {
        show_error_msg(
false , "Unable to initialize game.");
        
return   false ;
    }

    
if (! join_game())
    {
        show_error_msg(
false , "Unable to connect to server.");
        
return   false ;
    }

    
return   true ;
}

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

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

    DeleteCriticalSection(&m_update_cs);
}

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

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

    m_adapter.init();

    
// do not continue if quit selected
     if (! DialogBox(get_window_inst(), MAKEINTRESOURCE(IDD_CONNECT), g_hwnd, connect_dialog_proc))
        
return   false ;

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

    
return   true ;
}

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

BOOL CALLBACK connect_dialog_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);

        
// clear fields
        SetWindowText(GetDlgItem(dlg, IDC_HOSTIP), "192.168.0.100");
        SetWindowText(GetDlgItem(dlg, IDC_NAME), "");

        
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 ;

            
char  name[32], ip[16];

            
// make sure there is an ip entered
            GetWindowText(GetDlgItem(dlg, IDC_HOSTIP), ip,  sizeof (ip));

            
if (ip[0] == '\0')    // ip is NULL
                 break ;

            
// make sure there is a name entered
            GetWindowText(GetDlgItem(dlg, IDC_NAME), name,  sizeof (name));

            
if (name[0] == '\0')  // name is NULL
                 break ;

            g_app->set_info(g_adapter->get_guid(sel), ip, name);

            EndDialog(dlg, TRUE);
            
return  TRUE;
            }

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

    
return  FALSE;
}

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

void  cApp::set_info(GUID* adapter_guid,  const   char * host_ip,  const   char * player_name)
{
    m_adapter_guid = adapter_guid;

    strcpy(m_host_ip, host_ip);
    strcpy(m_player_name, player_name);
}

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

bool  cApp::init_game()
{
    create_display(g_hwnd, CLIENT_WIDTH, CLIENT_HEIGHT, 16, 
true true );
    set_perspective(D3DX_PI/4, 1.3333f, 1.0f, 10000.0f);

    ShowCursor(FALSE);

    create_font(&m_font, "Arial", 16, 
true false );

    m_input.create(g_hwnd, get_window_inst());
    m_keyboard.create_keyboard(&m_input);
    m_mouse.create_mouse(&m_input, 
true );

    
if (! m_terrain_mesh.load("..\\Data\\Arena.x", "..\\Data\\"))
        
return   false ;

    m_nodetree_mesh.create(&m_terrain_mesh, QUADTREE, 256, 32);

    
// load the meshes and animations

    
if (! m_char_mesh.load("..\\Data\\Warrior.x", "..\\Data\\"))
        
return   false ;

    
if (! m_weapon_mesh.load("..\\Data\\Sword.x", "..\\Data\\"))
        
return   false ;

    
if (! m_char_anim.load("..\\Data\\Warrior.x", &m_char_mesh))
        
return   false ;

    m_char_anim.set_loop(
true ,  "Walk");
    m_char_anim.set_loop(
true ,  "Idle");
    m_char_anim.set_loop(
false , "Swing");
    m_char_anim.set_loop(
false , "Hurt");

    m_cam_angle = 0.0f;

    m_players = 
new  sPlayer[MAX_PLAYERS];

    
// setup player data
     for ( long  i = 0; i < MAX_PLAYERS; i++)
    {
        m_players[i].body.create(&m_char_mesh);
        m_players[i].weapon.create(&m_weapon_mesh);
        m_players[i].weapon.attach_to_object(&m_players[i].body, "Bip01_R_Finger11");
        m_players[i].weapon.rotate(1.57f, 0.0f, 0.0f);
    }

    m_num_players = 1;

    
// setup local player structure
    m_players[0].connected  =  true ;
    m_players[0].direction  = 0.0f;
    m_players[0].x_pos      = 0.0f;
    m_players[0].y_pos      = 0.0f;
    m_players[0].z_pos      = 0.0f;
    m_players[0].speed      = 0.0f;
    m_players[0].last_state = STATE_IDLE;

    
return   true ;
}

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

bool  cApp::join_game()
{
    
// initialize network and try to connect to host

    m_client.init();

    
return  m_client.connect(m_adapter_guid, m_host_ip, 9123, m_player_name, "RPGGAME", NULL);
}

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

void  cApp::render_scene()
{
    
// center camera on player using camera angle
     float  x_eye = m_players[0].x_pos + cos(m_cam_angle) * 300.0f;
    
float  y_eye = m_players[0].y_pos + 100.0f;
    
float  z_eye = m_players[0].z_pos + sin(m_cam_angle) * 300.0f;

    m_camera.point(x_eye, y_eye, z_eye, m_players[0].x_pos, m_players[0].y_pos, m_players[0].z_pos);
    set_display_camera(&m_camera);

    cFrustum frustum;
    frustum.create(0.0f);

    clear_display(0, 1.0f);

    
if (begin_display_scene())
    {
        
// draw the terrain
        enable_zbuffer();
        m_nodetree_mesh.render(&frustum, 0.0f);

        
// draw all actiive and in view characters
         for ( long  i = 0; i < MAX_PLAYERS; i++)
        {
            
if (! m_players[i].connected)
                
continue ;

            
float  radius;
            m_players[i].body.get_bounds(NULL, NULL, NULL, NULL, NULL, NULL, &radius);

            
// bounds check if player in view
             if (frustum.is_sphere_in(m_players[i].x_pos, m_players[i].y_pos, m_players[i].z_pos, radius))
            {
                
// position character and rotate
                m_players[i].body.move(m_players[i].x_pos, m_players[i].y_pos, m_players[i].z_pos);
                m_players[i].body.rotate(0.0f, m_players[i].direction, 0.0f);

                
// render body and weapon
                m_players[i].body.update_anim(timeGetTime()/32,  true );
                m_players[i].body.render();
                m_players[i].weapon.render();
            }
        }

        end_display_scene();
    }

    present_display();
}
 

The Client’s Full Glory

The hard work is over! The only requirements for running the client are processing
the local player’s input and updating the players. Now, all you have to do is
spruce up your project with some 3-D graphics, and you’ll almost have a game.

The graphics portion of the client application uses the Graphics Core to draw the
various connected players in the game. You use a NodeTree object to render the
game’s level. The client loads all meshes when the application class is initialized. As
previously mentioned, all players receive an assigned mesh to represent their characters
and weapons. Animations are also used and are set by the various update messages.

You limit rendering of a scene to 30 times a second, and to ensure that everything
runs as quickly as possible, you use a viewing frustum to render the level and to clip
unseen characters out of the rendering loop.

To wrap up the Client application, you deal with the different kinds of application
code, such as selecting an adapter and connecting to the server.

 

Download source and solution
 

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