6.快节奏的多人游戏 (第六部分):Demo代码讲解(译者自加)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_25327609/article/details/88903102

1.Entity

代表客户端与服务端的传输模型,代表客户端的属性,服务端更新后将该对象返回客户端。

var Entity = function() {
	  this.x = 0;
	  this.speed = 2; // units/s
	  this.position_buffer = [];
	}

	// Apply user's input to this entity.
	Entity.prototype.applyInput = function(input) {
	  this.x += input.press_time*this.speed;
	}

2.LagNetwork

模拟网络连接,其中message数组用于装客户端或者服务端的消息,初始化时,模拟建立连接时就是客户端与服务端互相持有对方的LagNetwork 引用,从而实现数据的模拟传输。这里服务端发送数据时加上数据的延迟,并在客户端判断其时间小于当前时间时才能处理,从而实现模拟延迟的效果。

// =============================================================================
	//  A message queue with simulated network lag.
	// =============================================================================
	var LagNetwork = function() {
	  this.messages = [];
	}

	// "Send" a message. Store each message with the timestamp when it should be
	// received, to simulate lag.
	LagNetwork.prototype.send = function(lag_ms, message) {
	  this.messages.push({recv_ts: +new Date() + lag_ms,
	                      payload: message});
	}

	// Returns a "received" message, or undefined if there are no messages available
	// yet.
	LagNetwork.prototype.receive = function() {
	  var now = +new Date();
	  for (var i = 0; i < this.messages.length; i++) {
	    var message = this.messages[i];
	    if (message.recv_ts <= now) {
	      this.messages.splice(i, 1);
	      return message.payload;
	    }
	  }
	}

3.Client

3.1 Client类数据

持有相关启用功能的bool值等,以及持有的本地关于各个客户端的状态信息this.entities = {};

// =============================================================================
	//  The Client.
	// =============================================================================
	var Client = function(canvas, status) {
	  // Local representation of the entities.
	  this.entities = {};

	  // Input state.
	  this.key_left = false;
	  this.key_right = false;

	  // Simulated network connection.
	  this.network = new LagNetwork();
	  this.server = null;
	  this.lag = 0;

	  // Unique ID of our entity. Assigned by Server on connection.
	  this.entity_id = null;

	  // Data needed for reconciliation.
	  this.client_side_prediction = false;
	  this.server_reconciliation = false;
	  this.input_sequence_number = 0;
	  this.pending_inputs = [];

	  // Entity interpolation toggle.
	  this.entity_interpolation = true;

	  // UI.
	  this.canvas = canvas;
	  this.status = status;

	  // Update rate.
	  this.setUpdateRate(50);
	}

3.2 Client.Update

setInterval用来保证隔一定的间隔调用Client的Update

Client.prototype.setUpdateRate = function(hz) {
	  this.update_rate = hz;

	  clearInterval(this.update_interval);
	  this.update_interval = setInterval(
	    (function(self) { return function() { self.update(); }; })(this),
	    1000 / this.update_rate);
	}


	// Update Client state.
	Client.prototype.update = function() {
	  // Listen to the server.
	  this.processServerMessages();

	  if (this.entity_id == null) {
	    return;  // Not connected yet.
	  }

	  // Process inputs.
	  this.processInputs();

	  // Interpolate other entities.
	  if (this.entity_interpolation) {
	    this.interpolateEntities();
	  }

	  // Render the World.
	  renderWorld(this.canvas, this.entities);

	  // Show some info.
	  var info = "服务器未确认操作数目: " + this.pending_inputs.length;
	  this.status.textContent = info;
	}

3.3 Client.processServerMessages

存储或者更新服务端发送过来的状态,如果是本客户端自身的状态,若启用服务端协调则,删除服务端确认前的操作,客户端预测后面未确认的操作。若是其他客户端,则看是否启用插值,启用插值则将当前时间与位置添加到 entity.position_buffer中,方便后面插值操作。

// Process all messages from the server, i.e. world updates.
	// If enabled, do server reconciliation.
	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 this is the first time we see this entity, create a local representation.
	      if (!this.entities[state.entity_id]) {
	        var entity = new Entity();
	        entity.entity_id = state.entity_id;
	        this.entities[state.entity_id] = entity;
	      }

	      var entity = this.entities[state.entity_id];

	      if (state.entity_id == this.entity_id) {
	        // Received the authoritative position of this client's entity.
	        entity.x = state.position;

	        if (this.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.
	              entity.applyInput(input);
	              j++;
	            }
	          }
	        } else {
	          // Reconciliation is disabled, so drop all the saved inputs.
	          this.pending_inputs = [];
	        }
	      } else {
	        // Received the position of an entity other than this client's.
	   
	        if (!this.entity_interpolation) {
	          // Entity interpolation is disabled - just accept the server's position.
	          entity.x = state.position;
	        } else {
	          // Add it to the position buffer.
	          var timestamp = +new Date();
	          entity.position_buffer.push([timestamp, state.position]);
	        }
	      }
	    }
	  }
	}

3.4 Client.processInputs

将输入的信息封装后发送给服务端,若启用客户端预测则直接移动。并将当前输入放到待服务端确认的队列中。

	// 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(this.lag, input);

	  // Do client-side prediction.
	  if (this.client_side_prediction) {
	    this.entities[this.entity_id].applyInput(input);
	  }

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

3.5 Client.interpolateEntities

对于不是本玩家则插值,这也就是为什么-玩家看到的是现在的自己,看到的是过去的其他玩家。插值的实现为,将当前时间调到一个服务器周期前 var render_timestamp = now - (1000.0 / server.update_rate);,然后在这个时刻找到,前一个和后一个状态。然后在这两个状态间进行线性插值。

Client.prototype.interpolateEntities = function() {
	  // Compute render timestamp.
	  var now = +new Date(); 
	  var render_timestamp = now - (1000.0 / server.update_rate);

	  for (var i in this.entities) { 
	    var entity = this.entities[i];

	    // No point in interpolating this client's entity.
	    if (entity.entity_id == this.entity_id) {
	      continue;
	    }

	    // Find the two authoritative positions surrounding the rendering timestamp.
	    var buffer = entity.position_buffer;
	  
	    // Drop older positions.
	    while (buffer.length >= 2 && buffer[1][0] <= render_timestamp) {
	      buffer.shift();
	    }

	    // Interpolate between the two surrounding authoritative positions.
	    if (buffer.length >= 2 && buffer[0][0] <= render_timestamp && render_timestamp <= buffer[1][0]) {
	      var x0 = buffer[0][1];
	      var x1 = buffer[1][1];
	      var t0 = buffer[0][0];
	      var t1 = buffer[1][0];

	      entity.x = x0 + (x1 - x0) * (render_timestamp - t0) / (t1 - t0);
	    }
	  }
	}

4.Server

4.1 Server类数据

主要持有客户端以及客户端的状态

// =============================================================================
	//  The Server.
	// =============================================================================
	var Server = function(canvas, status) {
	  // Connected clients and their entities.
	  this.clients = [];
	  this.entities = [];

	  // Last processed input for each client.
	  this.last_processed_input = [];

	  // Simulated network connection.
	  this.network = new LagNetwork();

	  // UI.
	  this.canvas = canvas;
	  this.status = status;

	  // Default update rate.
	  this.setUpdateRate(10);
	}

4.2 Server .connect

即为将自己与各个客户端绑定,同时初始化各个玩家的相关数据。

Server.prototype.connect = function(client) {
	  // Give the Client enough data to identify itself.
	  client.server = this;
	  client.entity_id = this.clients.length;
	  this.clients.push(client);

	  // Create a new Entity for this Client.
	  var entity = new Entity();
	  this.entities.push(entity);
	  entity.entity_id = client.entity_id;

	  // Set the initial state of the Entity (e.g. spawn point)
	  var spawn_points = [4, 6];
	  entity.x = spawn_points[client.entity_id];
	}

	

4.3 Server .Update

处理客户端的输入,而非键盘事件,然后将更新结果返回给各个客户端。

Server.prototype.setUpdateRate = function(hz) {
	  this.update_rate = hz;

	  clearInterval(this.update_interval);
	  this.update_interval = setInterval(
	    (function(self) { return function() { self.update(); }; })(this),
	    1000 / this.update_rate);
	}

	Server.prototype.update = function() {
	  this.processInputs();
	  this.sendWorldState();
	  renderWorld(this.canvas, this.entities);
	}

4.4 Server .processInputs

更新服务端的客户端状态模型,更新最后一次确认号。并在空余时间更新UI显示。

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;
	    }

	  }

	  // Show some info.
	  var info = "服务端最近一次确认的操作序号: ";
	  for (var i = 0; i < this.clients.length; ++i) {
	    info += "玩家 " + i + ": #" + (this.last_processed_input[i] || 0) + "   ";
	  }
	  this.status.textContent = info;
	}

4.5 Server .sendWorldState

将广播客户端状态模型更新给各个客户端。

// Send the world state to all the connected clients.
	Server.prototype.sendWorldState = function() {
	  // Gather the state of the world. In a real app, state could be filtered to avoid leaking data
	  // (e.g. position of invisible enemies).
	  var world_state = [];
	  var num_clients = this.clients.length;
	  for (var i = 0; i < num_clients; i++) {
	    var entity = this.entities[i];
	    world_state.push({entity_id: entity.entity_id,
	                      position: entity.x,
	                      last_processed_input: this.last_processed_input[i]});
	  }

	  // Broadcast the state to all the clients.
	  for (var i = 0; i < num_clients; i++) {
	    var client = this.clients[i];
	    client.network.send(client.lag, world_state);
	  }
	}

5.最后

其他代码用于处理DOM,输入,以及绘制canvas,与逻辑无关,就不展开了。

猜你喜欢

转载自blog.csdn.net/qq_25327609/article/details/88903102