客户端预测问题

时间:2014-08-27 08:57:57

标签: c# networking xna

我正在尝试实现客户端预测和服务器协调的示例,如果有更好的方法请告诉我!我试图隐藏网络游戏的延迟。目前我使用lidgren和XNA作为客户端,只是服务器的控制台应用程序。我将服务器端的lidgren设置为模拟1.5秒的延迟。因此,当我运行此代码时,它在大多数情况下工作,但似乎客户端正在缓冲移动,然后最终在屏幕上移动角色但是根据我在下面引用的演示,我没有看到缓冲类型的行为我是只是难以理解它可能是什么?提前感谢您提供的任何帮助,如果您需要查看更多代码,请告诉我,我不想过多地填充帖子。

在SendMovement方法中,我接受用户输入并序列化命令以发送到服务器,它将继续并移动播放器并将移动命令存储在队列中。

    private Queue<MovementCommand> _previousMovements = new Queue<MovementCommand>(5000);

    public void SendMovement(float elapsed, byte direction)
    {
        MovementCommand movement = new MovementCommand(_id, _sequenceNumber++, elapsed, direction);

        OutputCommand<MovementCommand> moveCommand = new OutputCommand<MovementCommand>(GamePacketTypes.Movement, movement);

        byte[] msgData = moveCommand.Serialize();

        Send(msgData);

        Console.WriteLine(string.Format("Elapsed Time = {0}, Sequence # = {1}", elapsed, _sequenceNumber));

        if (_clientSidePrediction == true)
        {
            _player.Move(movement);

            _previousMovements.Enqueue(movement);

        }
    }

当我从服务器收到消息时,我更新了播放器位置,然后检查服务器的最后一个输入序列与本地序列号的比较。

    public void HandleMessages()
    {
        while (true)
        {
            if (mIncomingMessages.Count > 0)
            {
                for (int i = 0; i < mIncomingMessages.Count; i++)
                {
                    byte command = mIncomingMessages[i].ReadByte();


                    switch ((GamePacketTypes)command)
                    {
                        case GamePacketTypes.UpdateEntity:


                            EntityStateType stateObj = EntityStateType.Deserialize(mIncomingMessages[i].ReadBytes(mIncomingMessages[i].LengthBytes - 1));

                            _player.Position(stateObj);


                            if (_serverReconciliation == true)
                            {
                                if (stateObj.ID == _id)
                                {
                                    int j = 0;

                                    while (j < _previousMovements.Count)
                                    {
                                        MovementCommand move = _previousMovements.Peek();

                                        if (move.InputSequence <= stateObj.LastProcessedInput)
                                        {
                                            _previousMovements.Dequeue();
                                        }
                                        else
                                        {
                                            _player.Move(_previousMovements.Dequeue());
                                            j++;
                                        }

                                    }
                                }
                            }

                            break;

                    }

                }

                mIncomingMessages.Clear();
            }

            Thread.Sleep(25);
        }
    }

在服务器端,我只是从客户端接受命令,将其应用到他们的角色,并在下一次状态更新结束时为客户端设置最后处理的序列。

    private async Task<bool> HandleMovement(MovementCommand move)
    {
        switch((DirectionHeading)move.Direction)
        {
            case DirectionHeading.North:
                _player.Y -= (move.PressedTime * _player.Velocity);
                break;
            case DirectionHeading.East:
                _player.X += (move.PressedTime * _player.Velocity);
                break;
            case DirectionHeading.South:
                _player.Y += (move.PressedTime * _player.Velocity);
                break;
            case DirectionHeading.West:
                _player.X -= (move.PressedTime * _player.Velocity);

                break;

        }
        _player.Direction = move.Direction;
        LastProcessedInput = move.InputSequence;
        Console.WriteLine("Last Processed Input = {0}", LastProcessedInput);
        return true;
    }

来自Gabriel Gamebetta的示例代码(希望他不介意......)

// Get inputs and send them to the server.
// If enabled, do client-side prediction.
Client.prototype.processInputs = function() {
// Compute delta time since last update.
var now_ts = +new Date();
var last_ts = this.last_ts || now_ts;
var dt_sec = (now_ts - last_ts) / 1000.0;
this.last_ts = now_ts;

// Package player's input.
var input;
if (this.key_right) {
  input = { press_time: dt_sec };
} else if (this.key_left) {
  input = { press_time: -dt_sec };
} else {
  // Nothing interesting happened.
  return;
}

// Send the input to the server.
input.input_sequence_number = this.input_sequence_number++;
input.entity_id = this.entity_id;
this.server.network.send(client_server_lag, input);

// Do client-side prediction.
if (client_side_prediction) {
  this.entity.applyInput(input);
}

// Save this input for later reconciliation.
this.pending_inputs.push(input);
}


Server.prototype.processInputs = function() {
// Process all pending messages from clients.
while (true) {
var message = this.network.receive();
if (!message) {
  break;
}

// Update the state of the entity, based on its input.
// We just ignore inputs that don't look valid; this is what prevents
// clients from cheating.
if (this.validateInput(message)) {
  var id = message.entity_id;
  this.entities[id].applyInput(message);
  this.last_processed_input[id] = message.input_sequence_number;
 }
}

}

Client.prototype.processServerMessages = function() {
while (true) {
var message = this.network.receive();
if (!message) {
  break;
}

// World state is a list of entity states.
for (var i = 0; i < message.length; i++) {
  var state = message[i];

  if (state.entity_id == this.entity_id) {
    // Got the position of this client's entity.

    if (!this.entity) {
      // If this is the first server update, create a local entity.
      this.entity = new Entity();
    }

    // Set the position sent by the server.
    this.entity.x = state.position;

    if (server_reconciliation) {
      // Server Reconciliation. Re-apply all the inputs not yet processed by
      // the server.
      var j = 0;
      while (j < this.pending_inputs.length) {
        var input = this.pending_inputs[j];
        if (input.input_sequence_number <= state.last_processed_input) {
          // Already processed. Its effect is already taken into account
          // into the world update we just got, so we can drop it.
          this.pending_inputs.splice(j, 1);
        } else {
          // Not processed by the server yet. Re-apply it.
          this.entity.applyInput(input);
          j++;
        }
      }
    } else {
      // Reconciliation is disabled, so drop all the saved inputs.
      this.pending_inputs = [];
    }
  } else {
    // TO DO: add support for rendering other entities.
  }
}
}

1 个答案:

答案 0 :(得分:1)

不,我不介意:)但也许可以添加文章链接,为其他读者提供更多背景信息。

我不太了解C#,但在HandleMessages()的最里面的循环中,你在两个分支中使用Dequeue(),并且你在其中一个中递增j - 我有一种感觉,这是不正确的。您可能希望在服务器更新后多次重新应用输入。