我正在尝试实现客户端预测和服务器协调的示例,如果有更好的方法请告诉我!我试图隐藏网络游戏的延迟。目前我使用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.
}
}
}
答案 0 :(得分:1)
不,我不介意:)但也许可以添加文章链接,为其他读者提供更多背景信息。
我不太了解C#,但在HandleMessages()
的最里面的循环中,你在两个分支中使用Dequeue()
,并且你在其中一个中递增j
- 我有一种感觉,这是不正确的。您可能希望在服务器更新后多次重新应用输入。