Предисловие
Я пытался найти «Как сделать прогнозирование на стороне клиента и согласование сервера» с нуля с помощью простого для понимания кода на C #. Поэтому я решил создать свою собственную реализацию прогнозирования на стороне клиента и согласования сервера. Я хочу создать систему прогнозирования и согласования на стороне клиента, которую может использовать каждый. Я был бы признателен, если бы вы могли обсудить со мной лучший способ или способ, который все поймут.
Исходные файлы
Клиентская сторона: PlayerController.cs, ClientSend.cs и ClientHandler.cs Том Вейланд Ссылка на GitHub.
Сторона сервера: Server.cs, ServerHandler.cs и ServerSend.cs: Том Вейланд Ссылка на GitHub.
Ссылка, которую я прочитал, чтобы попытаться создать свою собственную реализацию: Spectre1989 Ссылка на GitHub
Код
Во-первых, в коде Spectre1989 сервер и клиент находятся в одном коде, а у Тома Вейланда нет, поэтому мне пришлось адаптировать код. Во-вторых, в коде Spectre1989 используется тип тика, но поскольку сервер и клиент находятся в одном коде, тик в клиенте и сервере начинает отсчитываться одновременно, и поэтому тик работает, но в коде Тома Вейланда , клиент и сервер запускаются в разное время, потому что они разделены, поэтому тиковая система не будет работать (или могла бы, но я сейчас не знаю, как этого добиться), поэтому я использовал вместо этого текущее время.
PlayerController.cs
private void Update()
{
float dt = Time.fixedDeltaTime;
float client_timer = this.client_timer;
client_timer += Time.deltaTime;
if (client_state_buffer.Any()) {client_state_buffer = client_state_buffer.Distinct().ToList();}
if (client_state_msgs.Any()) {client_state_msgs = client_state_msgs.Distinct().ToList();}
if (client_state_buffer.Any())
{
foreach (PlayerState state in client_state_buffer)
{
DateTime newDateTime = new DateTime();
newDateTime = newDateTime + (DateTime.Now - state.time);
if (newDateTime > Convert.ToDateTime("00:00:30"))
{
client_state_buffer.Remove(state);
}
}
}
if (client_state_msgs.Any())
{
foreach (PlayerState state in client_state_msgs)
{
DateTime newDateTime = new DateTime();
newDateTime = newDateTime + (DateTime.Now - state.time);
if (newDateTime > Convert.ToDateTime("00:00:30"))
{
client_state_msgs.Remove(state);
}
}
}
while(client_timer >= dt)
{
client_timer -= dt;
bool[] PlayerInput = CheckForInput();
DateTime TimeNow = Convert.ToDateTime(DateTime.Now.ToString("HH:mm:ss"));
InputState state = new InputState();
state.time = TimeNow;
state.inputs = PlayerInput;
if (!client_input_buffer.Contains(state) && PlayerInput.Contains(true)) {client_input_buffer.Add(state);}
PlayerState playerState = new PlayerState();
playerState.position = controller.gameObject.transform.position;
playerState.rotation = controller.gameObject.transform.rotation;
playerState.time = TimeNow;
Move(CalculateInputDirection(PlayerInput), controller, PlayerInput);
if (!client_state_buffer.Contains(playerState) && PlayerInput.Contains(true)) {client_state_buffer.Add(playerState);}
if (PlayerInput.Contains(true)) {ClientSend.PlayerMovement(state);}
}
this.client_timer = client_timer;
while (ClientHasStateMessage(client_state_msgs))
{
PlayerState server_state = client_state_msgs.OrderByDescending(x => x.time).FirstOrDefault();
PlayerState prev_state = client_state_buffer.OrderByDescending(x => x.time).FirstOrDefault();
if (server_state.time <= prev_state.time)
{
Vector3 position_error = Vector3.zero;
float rotation_error = 0f;
position_error = server_state.position - prev_state.position;
rotation_error = 1.0f - Quaternion.Dot(server_state.rotation, prev_state.rotation);
if (position_error.sqrMagnitude > 0.0000001f || rotation_error > 0.00001f)
{
gameObject.transform.position = server_state.position;
gameObject.transform.rotation = server_state.rotation;
}
client_state_msgs.Remove(server_state);
client_state_buffer.Remove(prev_state);
}
else
{
client_state_msgs.Remove(server_state);
}
}
}
ClientSend.cs
public static void PlayerMovement(PlayerController.InputState state)
{
using (Packet _packet = new Packet((int)ClientPackets.playerMovement))
{
_packet.Write(state.time.ToString());
_packet.Write(state.inputs.Length);
foreach (bool input in state.inputs)
{
_packet.Write(input);
}
SendUDPData(_packet);
}
}
ClientHandle.cs
public static void PlayerPosition(Packet _packet)
{
int _id = _packet.ReadInt();
DateTime time = Convert.ToDateTime(_packet.ReadString());
Vector3 _position = _packet.ReadVector3();
Quaternion _rotation = _packet.ReadQuaternion();
PlayerController.PlayerState state = new PlayerController.PlayerState();
state.time = time;
state.position = _position;
state.rotation = _rotation;
if (GameManager.players.TryGetValue(_id, out PlayerManager _player))
{
if (_player.gameObject.GetComponent<PlayerController>() != null)
{
_player.gameObject.GetComponent<PlayerController>().client_state_msgs.Add(state);
}
else
{
_player.transform.position = _position;
}
}
}
ServerHandle.cs
public static void PlayerMovement(int _fromClient, Packet _packet)
{
string time = _packet.ReadString();
bool[] inputs = new bool[_packet.ReadInt()];
for (int i = 0; i < inputs.Length; i++)
{
inputs[i] = _packet.ReadBool();
}
Server.InputState state = new Server.InputState();
state.time = time;
state.inputs = inputs;
Server.SimulatePlayerInput(state, _fromClient);
}
ServerSend.cs
public static void PlayerPosition(Player _player, Server.PlayerState state_msg)
{
using (Packet _packet = new Packet((int)ServerPackets.playerPosition))
{
_packet.Write(_player.id);
_packet.Write(state_msg.time);
_packet.Write(state_msg.position);
_packet.Write(state_msg.rotation);
SendUDPDataToAll(_packet);
}
}
Server.cs
public static void SimulatePlayerInput(InputState state, int _fromClient)
{
MovePlayer(CalculateInputDirection(state.inputs), clients[_fromClient].player, state.inputs);
PlayerState state_msg = new PlayerState();
state_msg.time = DateTime.Now.ToString("HH:mm:ss");
state_msg.position = clients[_fromClient].player.gameObject.transform.position;
state_msg.rotation = clients[_fromClient].player.gameObject.transform.rotation;
ServerSend.PlayerPosition(clients[_fromClient].player, state_msg);
}