[Change] Switched to an input-based reconcilation
Replaced the previous correction models with a full "Reconciliation by Replaying Inputs system".
This commit is contained in:
parent
8d59e79f8a
commit
f653c34baf
@ -4,7 +4,7 @@ namespace BettolaLib {
|
||||
namespace Network {
|
||||
|
||||
enum class MessageType : unsigned char {
|
||||
PlayerPosition,
|
||||
PlayerInput,
|
||||
PlayerState,
|
||||
GameState,
|
||||
PlayerId,
|
||||
|
||||
@ -5,10 +5,14 @@
|
||||
namespace BettolaLib {
|
||||
namespace Network {
|
||||
|
||||
struct PlayerPosMessage {
|
||||
struct PlayerInputMessage {
|
||||
unsigned int player_id;
|
||||
float x;
|
||||
float y;
|
||||
unsigned int sequence_number;
|
||||
bool up;
|
||||
bool down;
|
||||
bool left;
|
||||
bool right;
|
||||
float dt;
|
||||
};
|
||||
|
||||
} /* namespace Network. */
|
||||
@ -9,6 +9,7 @@ struct PlayerStateMessage {
|
||||
unsigned int player_id;
|
||||
float x;
|
||||
float y;
|
||||
unsigned int last_processed_sequence;
|
||||
};
|
||||
|
||||
} /* namespace Network. */
|
||||
|
||||
@ -20,7 +20,7 @@
|
||||
#include "math/mat4.h"
|
||||
#include "network/message.h"
|
||||
#include "network/game_state_message.h"
|
||||
#include "network/player_pos_message.h"
|
||||
#include "network/player_input_message.h"
|
||||
|
||||
/* Dacav's resolution ;) */
|
||||
const int SCREEN_WIDTH = 800;
|
||||
@ -33,9 +33,7 @@ Bettola::Bettola(void) :
|
||||
_vao(0),
|
||||
_vbo(0),
|
||||
_our_player_id(0),
|
||||
_needs_correction(false),
|
||||
_correction_target_x(0.0f),
|
||||
_correction_target_y(0.0f) {
|
||||
_input_sequence_number(0) {
|
||||
|
||||
memset(&_server_addr, 0, sizeof(_server_addr));
|
||||
}
|
||||
@ -203,41 +201,33 @@ void Bettola::update(double dt) {
|
||||
|
||||
_player.update(dt);
|
||||
|
||||
if(_needs_correction) {
|
||||
const float interp_speed = 20.0f; /* We'll use a slightly higher speed for self-correction. */
|
||||
float current_x = _player.get_x();
|
||||
float current_y = _player.get_y();
|
||||
_player.set_position(current_x + (_correction_target_x - current_x) * interp_speed * dt,
|
||||
current_y + (_correction_target_y - current_y) * interp_speed * dt);
|
||||
|
||||
/* If we are very close.. Stop correcting. */
|
||||
if(std::abs(current_x - _correction_target_x) < 0.1f &&
|
||||
std::abs(current_y - _correction_target_y) < 0.1f) {
|
||||
_needs_correction = false;
|
||||
}
|
||||
}
|
||||
|
||||
for(auto& remote_player : _remote_players) {
|
||||
remote_player.update(dt);
|
||||
}
|
||||
|
||||
if(_our_player_id > 0) {
|
||||
/* Send player position to server via UDP.. */
|
||||
/* Sent our current input state to the server. */
|
||||
BettolaLib::Network::PlayerInputMessage input_msg;
|
||||
input_msg.player_id = _our_player_id;
|
||||
input_msg.sequence_number = ++_input_sequence_number;
|
||||
input_msg.up = _input.up;
|
||||
input_msg.down = _input.down;
|
||||
input_msg.left = _input.left;
|
||||
input_msg.right = _input.right;
|
||||
input_msg.dt = dt;
|
||||
|
||||
BettolaLib::Network::MessageHeader header;
|
||||
header.type = BettolaLib::Network::MessageType::PlayerPosition;
|
||||
header.size = sizeof(BettolaLib::Network::PlayerPosMessage);
|
||||
header.type = BettolaLib::Network::MessageType::PlayerInput;
|
||||
header.size = sizeof(input_msg);
|
||||
|
||||
BettolaLib::Network::PlayerPosMessage msg;
|
||||
msg.player_id = _our_player_id;
|
||||
msg.x = _our_player_id;
|
||||
msg.x = _player.get_x();
|
||||
msg.y = _player.get_y();
|
||||
|
||||
char buffer[sizeof(header) + sizeof(msg)];
|
||||
char buffer[sizeof(header) + sizeof(input_msg)];
|
||||
memcpy(buffer, &header, sizeof(header));
|
||||
memcpy(buffer + sizeof(header), &msg, sizeof(msg));
|
||||
memcpy(buffer+sizeof(header), &input_msg, sizeof(input_msg));
|
||||
|
||||
_udp_socket.send_to(buffer, sizeof(buffer), _server_addr);
|
||||
|
||||
/* Store for reconciliation. */
|
||||
_pending_inputs.push_back(input_msg);
|
||||
}
|
||||
|
||||
static char window_title[256];
|
||||
@ -303,22 +293,24 @@ void Bettola::process_game_state(const BettolaLib::Network::GameStateMessage& ms
|
||||
const auto& player_state = msg.players[i];
|
||||
|
||||
if(player_state.player_id == _our_player_id) {
|
||||
/*
|
||||
* This is our player. Reconcile predicted state with the
|
||||
* server's authoritative state.
|
||||
*/
|
||||
printf(" -> Processing remote player %u at (%.2f, %.2f)\n", player_state.player_id,
|
||||
player_state.x, player_state.y);
|
||||
/* This is us! Reconcile our predicted state. */
|
||||
float dx = _player.get_x() - player_state.x;
|
||||
float dy = _player.get_y() - player_state.y;
|
||||
if((dx*dx+dy*dy) > 0.0001f) { /* If distance is not negligibale. */
|
||||
/* Simple correction, snap to server position. */
|
||||
//_player.set_position(player_state.x, player_state.y);
|
||||
/* This is our player. Reconcile. */
|
||||
_player.set_position(player_state.x, player_state.y);
|
||||
|
||||
_needs_correction = true;
|
||||
_correction_target_x = player_state.x;
|
||||
_correction_target_y = player_state.y;
|
||||
/* Remove all inputs from our history that the server has processed. */
|
||||
while(!_pending_inputs.empty() &&
|
||||
_pending_inputs.front().sequence_number <= player_state.last_processed_sequence) {
|
||||
|
||||
_pending_inputs.pop_front();
|
||||
}
|
||||
|
||||
/* Now, re-apply any remaining inputs that the server hasn't
|
||||
* seen yet. This'll bring the client up to date with most recent inputs.
|
||||
*/
|
||||
for(const auto& input : _pending_inputs) {
|
||||
float dir_x = (input.right ? 1.0f : 0.0f) - (input.left ? 1.0f : 0.0f);
|
||||
float dir_y = (input.down ? 1.0f : 0.0f) - (input.up ? 1.0f : 0.0f);
|
||||
_player.set_velocity_direction(dir_x, dir_y);
|
||||
_player.update(input.dt);
|
||||
}
|
||||
} else {
|
||||
/* Remote player. Find if we already know about them.. */
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <netinet/in.h>
|
||||
#include <deque>
|
||||
#include <SDL3/SDL.h>
|
||||
#include <vector>
|
||||
|
||||
@ -9,6 +10,7 @@
|
||||
#include "game/remote_player.h"
|
||||
#include "math/mat4.h"
|
||||
#include "network/tcpsocket.h"
|
||||
#include "network/player_input_message.h"
|
||||
#include "network/udpsocket.h"
|
||||
namespace BettolaLib { namespace Network { struct GameStateMessage; } }
|
||||
|
||||
@ -61,7 +63,6 @@ private:
|
||||
BettolaLib::Network::UDPSocket _udp_socket;
|
||||
sockaddr_in _server_addr;
|
||||
|
||||
bool _needs_correction;
|
||||
float _correction_target_x;
|
||||
float _correction_target_y;
|
||||
unsigned int _input_sequence_number;
|
||||
std::deque<BettolaLib::Network::PlayerInputMessage> _pending_inputs;
|
||||
};
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
|
||||
#include "game.h"
|
||||
#include "network/game_state_message.h"
|
||||
#include "network/player_pos_message.h"
|
||||
#include "network/player_input_message.h"
|
||||
#include "network/tcpsocket.h"
|
||||
#include "network/message.h"
|
||||
#include "network/udpsocket.h"
|
||||
@ -27,12 +27,18 @@ void Game::remove_player(unsigned int player_id) {
|
||||
_players.end());
|
||||
}
|
||||
|
||||
void Game::handle_udp_message(const BettolaLib::Network::MessageHeader& header,
|
||||
const char* buffer, const sockaddr_in& from_addr) {
|
||||
void Game::process_udp_message(const char* buffer, size_t size, const sockaddr_in& from_addr) {
|
||||
if(size < sizeof(BettolaLib::Network::MessageHeader)) return;
|
||||
|
||||
if(header.type == BettolaLib::Network::MessageType::PlayerPosition) {
|
||||
BettolaLib::Network::PlayerPosMessage msg;
|
||||
memcpy(&msg, buffer, sizeof(msg));
|
||||
BettolaLib::Network::MessageHeader header;
|
||||
memcpy(&header, buffer, sizeof(header));
|
||||
|
||||
if(header.type == BettolaLib::Network::MessageType::PlayerInput) {
|
||||
if(size < sizeof(BettolaLib::Network::MessageHeader)
|
||||
+ sizeof(BettolaLib::Network::PlayerInputMessage)) return;
|
||||
|
||||
BettolaLib::Network::PlayerInputMessage msg;
|
||||
memcpy(&msg, buffer + sizeof(header), sizeof(msg));
|
||||
|
||||
Player* player = get_player_by_id(msg.player_id);
|
||||
if(player) {
|
||||
@ -40,7 +46,11 @@ void Game::handle_udp_message(const BettolaLib::Network::MessageHeader& header,
|
||||
printf("Bettola Server: Associated UDP address for player %u\n", msg.player_id);
|
||||
player->set_udp_addr(from_addr);
|
||||
}
|
||||
player->set_position(msg.x, msg.y);
|
||||
player->set_last_processed_sequence(msg.sequence_number);
|
||||
float dir_x = (msg.right ? 1.0f : 0.0f) - (msg.left ? 1.0f : 0.0f);
|
||||
float dir_y = (msg.down ? 1.0f : 0.0f) - (msg.up ? 1.0f : 0.0f);
|
||||
player->set_velocity_direction(dir_x, dir_y);
|
||||
player->update(msg.dt);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -54,6 +64,7 @@ void Game::broadcast_game_state(BettolaLib::Network::UDPSocket& udp_socket) {
|
||||
msg.players[i].player_id = _players[i]->get_id();
|
||||
msg.players[i].x = _players[i]->get_x();
|
||||
msg.players[i].y = _players[i]->get_y();
|
||||
msg.players[i].last_processed_sequence = _players[i]->get_last_processed_sequence();
|
||||
}
|
||||
|
||||
BettolaLib::Network::MessageHeader header;
|
||||
|
||||
@ -12,8 +12,7 @@ public:
|
||||
Player* add_player(BettolaLib::Network::TCPSocket* socket);
|
||||
void remove_player(unsigned int player_id);
|
||||
|
||||
void handle_udp_message(const BettolaLib::Network::MessageHeader& header,
|
||||
const char* buffer, const sockaddr_in& from_addr);
|
||||
void process_udp_message(const char* buffer, size_t size, const sockaddr_in& from_addr);
|
||||
void broadcast_game_state(BettolaLib::Network::UDPSocket& udp_socket);
|
||||
|
||||
Player* get_player_by_socket(const BettolaLib::Network::TCPSocket* socket);
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
|
||||
#include "player.h"
|
||||
#include "network/tcpsocket.h"
|
||||
@ -14,7 +15,25 @@ Player::Player(BettolaLib::Network::TCPSocket* socket) :
|
||||
_next_player_id = 1;
|
||||
}
|
||||
_id = _next_player_id++;
|
||||
_x = 0.0f; _y = 0.0f;
|
||||
_x = 0.0f; _y = 0.0f;
|
||||
_vx = 0.0f; _vy = 0.0f;
|
||||
_speed = 200.0f; /* Must match client! */
|
||||
_last_processed_sequence = 0;
|
||||
memset(&_udp_addr, 0, sizeof(_udp_addr));
|
||||
}
|
||||
|
||||
void Player::update(double dt) {
|
||||
_x += _vx * dt;
|
||||
_y += _vy * dt;
|
||||
}
|
||||
|
||||
void Player::set_velocity_direction(float dir_x, float dir_y) {
|
||||
if(dir_x == 0.0f && dir_y == 0.0f) {
|
||||
_vx = _vy = 0.0f;
|
||||
} else {
|
||||
float mag = sqrt(dir_x * dir_x + dir_y * dir_y);
|
||||
_vx = (dir_x / mag) * _speed;
|
||||
_vy = (dir_y / mag) * _speed;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -8,6 +8,8 @@ class Player {
|
||||
public:
|
||||
Player(BettolaLib::Network::TCPSocket* socket);
|
||||
|
||||
void update(double dt);
|
||||
void set_velocity_direction(float dir_x, float dir_y);
|
||||
void set_position(float x, float y) { _x = x; _y = y; }
|
||||
void set_udp_addr(const sockaddr_in& addr) { _udp_addr = addr; _has_udp_addr = true; }
|
||||
|
||||
@ -18,6 +20,8 @@ public:
|
||||
BettolaLib::Network::TCPSocket& get_socket(void) const { return *_socket; }
|
||||
const sockaddr_in& get_udp_addr(void) const { return _udp_addr; }
|
||||
bool has_udp_addr(void) const { return _has_udp_addr; }
|
||||
void set_last_processed_sequence(unsigned int sequence) { _last_processed_sequence = sequence; }
|
||||
unsigned int get_last_processed_sequence(void) const { return _last_processed_sequence; }
|
||||
|
||||
private:
|
||||
static unsigned int _next_player_id;
|
||||
@ -28,4 +32,7 @@ private:
|
||||
BettolaLib::Network::TCPSocket* _socket;
|
||||
sockaddr_in _udp_addr;
|
||||
bool _has_udp_addr;
|
||||
unsigned int _last_processed_sequence;
|
||||
float _vx, _vy;
|
||||
float _speed;
|
||||
};
|
||||
|
||||
10
srv/main.cpp
10
srv/main.cpp
@ -12,7 +12,7 @@
|
||||
#include "bettola/network/udpsocket.h"
|
||||
#include "bettola/network/net_common.h"
|
||||
#include "bettola/network/message.h"
|
||||
#include "bettola/network/player_pos_message.h"
|
||||
#include "bettola/network/player_input_message.h"
|
||||
#include "game/game.h"
|
||||
|
||||
int main(void) {
|
||||
@ -126,13 +126,7 @@ int main(void) {
|
||||
ssize_t bytes_received;
|
||||
|
||||
while((bytes_received = udp_socket.recv_from(buffer, sizeof(buffer), from_addr)) > 0) {
|
||||
if(bytes_received >= sizeof(BettolaLib::Network::MessageHeader)) {
|
||||
BettolaLib::Network::MessageHeader header;
|
||||
memcpy(&header, buffer, sizeof(header));
|
||||
if(bytes_received >= sizeof(BettolaLib::Network::MessageHeader) + header.size) {
|
||||
game.handle_udp_message(header, buffer+sizeof(BettolaLib::Network::MessageHeader), from_addr);
|
||||
}
|
||||
}
|
||||
game.process_udp_message(buffer, bytes_received, from_addr);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user