Создание прогноза на стороне клиента и согласования сервера

Предисловие

Я пытался найти «Как сделать прогнозирование на стороне клиента и согласование сервера» с нуля с помощью простого для понимания кода на 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);      
    }

0

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *