Getting Online with Multiplayer Gaming(15)

Getting Online with Multiplayer Gaming(15)

 

Working with Game Clients

The client application (referred to as the client) is the conduit between the gaming
server and the player. The client accepts the user’s input and forwards it to the server.
Between updates from the server, the client updates itself based on what little information
it has—the player’s movement, other players’ movements, NPC actions, and so on.

The client uses graphics, sound, and input-processing to work its magic. However,
if you were to strip away the graphics and sound, you would be left with a rather
bland application. This “dumb” client structure might look unworthy, but believe
me, it will work perfectly for your game project.

To use the Client application, you can follow these steps:

1. Locate and run the Client application. The Connect to Server dialog box
(shown in Following snap) appears.

Getting Online with Multiplayer Gaming(15)_第1张图片 Besides picking an adapter and entering a player name, you’ll need to know the server’s IP address in order to connect and play the game.

2. In the Connect to Server dialog box, enter the host’s IP address, select an
adapter, and enter your player’s name.

3. Click OK to begin the game and connect to the server.

The client works almost identically to the server in some respects, the first of which
is dealing with players.

 

Handling Player Data

The client, much like the server, uses an sPlayer structure that contains the information
about each connected player in the game. This time, however, information is
needed to track the 3-D object for drawing the player (as well as the weapon) and
the player animation being played. Other than that, you can see many similarities
between the sPlayer structure being used by the client and server. Take a look at
the declaration of the client’s sPlayer structure (along with supporting macros):

#define  MAX_PLAYERS           8      // Maximum number of players allowed to be connected at once

#define  STATE_IDLE            1 
#define  STATE_MOVE            2
#define  STATE_SWING           3
#define  STATE_HURT            4

#define  ANIM_IDLE             1
#define  ANIM_WALK             2
#define  ANIM_SWING            3
#define  ANIM_HURT             4

typedef 
struct  sPlayer
{
    
bool     connected;    
    DPNID   player_id;

    
long     last_state;      
    
long     last_update_time;
    
long     latency;

    
float    x_pos, y_pos, z_pos;
    
float    direction;
    
float    speed;

    cObject body;
    cObject weapon;
    
long     last_anim;

    
///////////////////////////////////////////////////////////////
    
    sPlayer()
    {
        connected        = 
false ;
        player_id        = 0;
        last_anim        = 0;
        last_update_time = 0;
    }
} *sPlayerPtr;

Again, an array of sPlayer structures is allocated to hold the player information.
Each player is allowed to use a separate Graphics Core object for the character’s
body and weapon mesh. The local player uses the first element in the player data
array (defined as m_players in the application class), although joining players are
stuffed into the first empty slot found.

#define  MSG_CREATE_PLAYER     1
#define  MSG_GET_PLAYER_INFO   2
#define  MSG_DESTROY_PLAYER    3
#define  MSG_STATE_CHANGE      4

typedef 
struct  sMsgHeader
{
    
long     type;        // type of message (MSG_*)
     long     size;        // size of data to send
    DPNID   player_id;   // player performing action
} *sMsgHeaderPtr;

typedef 
struct  sMsg      // The message queue message structure
{
    sMsgHeader  header;
    
char         data[512];
} *sMsgPtr;

typedef 
struct  sCreatePlayerMsg
{
    sMsgHeader  header;
    
float        x_pos, y_pos, z_pos;     // Create player coordinates
     float        direction;
} *sCreatePlayerMsgPtr;

typedef 
struct  sRequestPlayerInfoMsg
{
    sMsgHeader  header;
    DPNID       request_player_id;      
// which player to request
} *sRequestPlayerInfoMsgPtr;

typedef 
struct  sDestroyPlayerMsg
{
    sMsgHeader header;
} *sDestroyPlayerMsgPtr;

typedef 
struct  sStateChangeMsg
{
    sMsgHeader  header;
    
    
long         state;                   // State message (STATE_*)
     float        x_pos, y_pos, z_pos;
    
float        direction;
    
float        speed;
    
long         latency;
} *sStateChangeMsgPtr;

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

class  cApp :  public  cFramework
{
private :
    CRITICAL_SECTION    m_update_cs;    

    cInput              m_input;
    cInputDevice        m_keyboard;
    cInputDevice        m_mouse;

    cMesh               m_terrain_mesh;
    cNodeTreeMesh       m_nodetree_mesh;

    cMesh               m_char_mesh;
    cMesh               m_weapon_mesh;
    cAnimation          m_char_anim;

    cCamera             m_camera;
    
float                m_cam_angle;
    
    cNetworkAdapter     m_adapter;
    cClient             m_client;
    GUID*               m_adapter_guid;

    
char                 m_host_ip[16];
    
char                 m_player_name[32];

    
long                 m_num_players;
    sPlayer*            m_players;    

    ID3DXFont*          m_font;

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

public :
    cApp();

    
virtual   bool  init();
    
virtual   bool  frame();
    
virtual   void  shutdown();
    
    
bool  receive( const  DPNMSG_RECEIVE* msg);
    
void  set_info(GUID* adapter_guid,  const   char * host_ip,  const   char * player_name);

    
void  set_local_player(DPNID player_id);

private :
    
bool  select_adapter();
    
bool  init_game();
    
bool  join_game();    

    
void  update_all_players();
    
void  render_scene();

    
bool  send_network_msg( void * msg,  long  send_flags);
    
long  get_player_index(DPNID player_id);

    
void  create_player( const  sMsg* msg);
    
void  destroy_player( const  sMsg* msg);
    
void  change_player_state( const  sMsg* msg);
};

BOOL CALLBACK connect_dialog_proc(HWND dlg, UINT msg, WPARAM wParam, LPARAM lParam);

As the application class for the client is initialized, all character and weapon
meshes are loaded and assigned to each of the player data structures. This is your
first chance to customize your network game; by loading different meshes, you can
have each player appear differently. For example, one character can be a warrior,
another character a wizard, and so on.

A list of animations is also loaded. Those animations represent the various states of
players: a walking animation, standing still (idle), swinging a weapon, and finally a
hurt animation. Those animations are set by the update_all_players function, which you
see in a bit in the section “Updating Local Players.”

One extra tidbit in the sPlayer structure is a DirectPlay identification number.
Clients normally don’t have access to their identification numbers; those are left
for the server to track. However, clients are designed so that their identification
numbers track all players, and in order to start playing, all clients must request
their identification number from the server.

When a game message is received from the server, the client application scans
through the list of connected players. When the player identification number from
the local list of players and from the server is matched, the client knows exactly
which player to update.

The client uses a function called get_player_index to scan the list of players and return
the index number of the matching player (or -1 if no such match is found):

long  cApp::get_player_index(DPNID player_id)
{
    
// scan list looking for match
     for ( long  i = 0; i < MAX_PLAYERS; i++)
    {
        
if (m_players[i].player_id == player_id && m_players[i].connected)
            
return  i;
    }

    
return  -1;   // no found in list
}

From now on, the client will always use the get_player_index function to determine
which player to update. If a player is not found in the list but is known to exist, the
client must send a MSG_GET_PLAYER_INFO message, which requests the player’s information
from the server. In response, the server will return a create-player message to the
requesting client.

But I’m getting a little ahead of myself, so let’s slow things down a bit. Much like
the server, the client uses the Network Core to handle network communications.
Now, take a look at the client component I’m using for the client application.

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