Getting Online with Multiplayer Gaming(18)
Updating the Local Player
Between updates from the server, the clients need to update all players to keep the
game running smoothly. The client application limits updates to every 33ms (30 times
a second), which matches the server update rate. Between these player updates, the
client is allowed to collect input from the player who is used to change their actions.
The cApp::frame function is generally used to update the local player. The players
use the keyboard and mouse to control their characters, so I included a few Input
Core objects (m_keyboard and m_mouse):
{
// get local input every frame
m_keyboard.acquire();
m_mouse.acquire();
m_keyboard.read();
m_mouse.read();
// handle connection screen
if (!g_connected || m_players[0].player_id == 0)
{
// display connection message
clear_display(0, 1.0f);
if (begin_display_scene())
{
draw_font(m_font, "Connecting to server ", 0, 0, 0, 0, COLOR_WHITE, DT_LEFT);
end_display_scene();
}
present_display();
return true ;
}
// store movements every frame
static long move_action = 0, last_move = 0;
if (m_keyboard.get_key_state(KEY_UP) || m_keyboard.get_key_state(KEY_W))
move_action |= ACTION_MOVE_UP;
if (m_keyboard.get_key_state(KEY_RIGHT) || m_keyboard.get_key_state(KEY_D))
move_action |= ACTION_MOVE_RIGHT;
if (m_keyboard.get_key_state(KEY_DOWN) || m_keyboard.get_key_state(KEY_S))
move_action |= ACTION_MOVE_DOWN;
if (m_keyboard.get_key_state(KEY_LEFT) || m_keyboard.get_key_state(KEY_A))
move_action |= ACTION_MOVE_LEFT;
// store attack action
if (m_keyboard.get_key_state(KEY_SPACE) || m_mouse.get_button_state(MOUSE_LBUTTON))
move_action |= ACTION_ATTACK;
// rotate camera
static bool cam_moved = false ;
if (m_mouse.get_x_delta() > 0)
{
m_cam_angle -= 0.1f;
cam_moved = true ;
}
if (m_mouse.get_x_delta() < 0)
{
m_cam_angle += 0.1f;
cam_moved = true ;
}
static DWORD update_counter = timeGetTime();
// only update players every 33ms (30 times a second)
if (timeGetTime() < update_counter + 33)
return true ;
// set flag to allow player movement
bool allow_move = true ;
// do not allow movement if still swinging weapon or being hurt
if (m_players[0].last_state == STATE_SWING || m_players[0].last_state == STATE_HURT)
allow_move = false ;
// handle movements if allowed
if (allow_move)
{
// process attack
if (move_action & ACTION_ATTACK)
{
move_action = 0; // clear movement
last_move = 0; // clear last movement
// send attack message - let server signal swing
sStateChangeMsg change_msg;
change_msg.header.type = MSG_STATE_CHANGE;
change_msg.header.size = sizeof (sStateChangeMsg);
change_msg.header.player_id = m_players[0].player_id;
change_msg.state = STATE_SWING;
change_msg.direction = m_players[0].direction;
send_network_msg(&change_msg, DPNSEND_NOLOOPBACK);
}
// process local player movements
if (move_action > 0 && move_action < 13)
{
// set new player state
EnterCriticalSection(&m_update_cs);
m_players[0].last_state = STATE_MOVE;
m_players[0].direction = g_angles[move_action] - m_cam_angle + 4.71f;
LeaveCriticalSection(&m_update_cs);
// reset last move if camera moved since last update
if (cam_moved)
{
cam_moved = false ;
last_move = 0;
}
// send actions to server if changed from last move
if (move_action != last_move)
{
last_move = move_action; // store last action
m_players[0].last_update_time = timeGetTime();
sStateChangeMsg change_msg;
// construct message
change_msg.header.type = MSG_STATE_CHANGE;
change_msg.header.size = sizeof (sStateChangeMsg);
change_msg.header.player_id = m_players[0].player_id;
change_msg.state = STATE_MOVE;
change_msg.direction = m_players[0].direction;
send_network_msg(&change_msg, DPNSEND_NOLOOPBACK);
}
}
else
{
// change to idle state
EnterCriticalSection(&m_update_cs);
m_players[0].last_state = STATE_IDLE;
LeaveCriticalSection(&m_update_cs);
// send update only if player moved last update
if (last_move)
{
last_move = 0;
sStateChangeMsg change_msg;
change_msg.header.type = MSG_STATE_CHANGE;
change_msg.header.size = sizeof (sStateChangeMsg);
change_msg.header.player_id = m_players[0].player_id;
change_msg.state = STATE_IDLE;
change_msg.direction = m_players[0].direction;
send_network_msg(&change_msg, DPNSEND_NOLOOPBACK);
}
}
}
update_all_players();
render_scene();
move_action = 0; // clear action data for next frame
update_counter = timeGetTime(); // reset update counter
return true ;
}
At every frame, the input devices are restored (in case a device’s focus has been
lost), and input is read in. If the user presses Esc, the game-play quits by returning
a value of false from the frame function.
From here, game-play may only continue if the client is connected to the server.
If no such connection exists, a message displays to that effect. Also, if a player is
still waiting for a DirectPlay identification number from the server, a message displays,
and a request is periodically sent to the server for the correct identification
number.
From here on, player input is parsed. A single variable tracks player actions (move_action),
and each bit in the variable represents a specific action (as shown in Figure 19.17). The
user’s actions are move up, move down, move left, move right, and attack. Also, camera
angle changes are recorded (and flagged for later updating).
Normally, players are allowed to move around the world, but if a player is currently
swinging his weapon or being hurt, that player is not allowed to move. You use the
allow_move flag to signify when a player’s actions can be processed, as shown here:
If a player chooses to attack, you need to construct a state-change message and
send that message to the server. After you send the state-change message, clear the
player’s movement actions. Notice that the client does not change its own state at
this point; the server determines when to change the player’s state.
If the player did not attack, his actions are checked to see whether the player is
moving.
After the player’s state and movement direction is set, the Frame function continues
by resetting the camera’s movements (by setting the cam_move flag to false). The
player’s controls are relative to the camera-viewing angle (if the player is pressing
the up arrow key, he is walking away from the camera). If you change the camera’s
angle while the player is walking, you force the player’s direction to change as well.
The client takes this change of the player’s direction into consideration when the
camera is rotated.
Once a player has moved, the client sends a state-change message to the server.
Notice that the state-change message is sent only if the player’s movement is different
from the last move he performed (as recorded in the last_move variable).
If the player hasn’t moved, his state is changed to standing still (STATE_IDLE), and a
state-change message is sent to the server.
At this point, the local player’s actions have been recorded and sent to the server.
Next, all players are updated, the scene is rendered, and the movement actions are
reset for the next frame.