游戏邦在:
杂志专栏:
gamerboom.com订阅到鲜果订阅到抓虾google reader订阅到有道订阅到QQ邮箱订阅到帮看

如何用非命令式P2P方法执行联网游戏

发布时间:2013-08-28 17:22:28 Tags:,,,

作者:Fernando Bevilacqua

玩多人游戏总是更有趣,因为玩家面对的不是AI控制的对手,而是另一名真人玩家。本教程将介绍如何使用非命令式P2P方式执行一款多人联网游戏。

注:尽管本教程中的案例是用AS3和Flash制作的,你仍然可以在几乎任何游戏开发环境中使用相同的技术和概念。另外,你必须掌握网络通信的基础知识。

简介

联网多人游戏具有若干不同的执行办法,可分为两类:命令式的和非命令式的。

对于命令式,最常用的方法是客户端服务器架构,即由中央实体(命令服务器)控制整个游戏。每一个连接到服务器的客户端都要不断地接收数据,然后本地生成游戏状态并显示出来。这有点像看电视。

img1_server_client(from gamedev.tutsplus)

img1_server_client(from gamedev.tutsplus)

(使用客户端服务器架构的命令式执行方法)

如果客户端执行一个动作,如从一个点移动到另一个地,那么这个信息就会发送给服务器。服务器查看这个信息是否正确,然后更新它的游戏状态。之后,服务器把信息传送给所有客户端,这样它们就可以相应地更新自己的游戏状态。

对于非命令式,没有中央实体,各个P(游戏邦注:这里的“P”是指联网中的电脑运行的游戏)控制自己的游戏状态。在P2P方法中,P发送数据给所有其他P,并接收来自它们的数据,假设那些信息都是正确可靠的(无作弊):

img2_p2p(from gamedev.tutsplus)

img2_p2p(from gamedev.tutsplus)

(使用P2P架构的非命令式执行方法)

在本教程中,我将介绍如何使用非命令式P2P办法执行多人联网游戏。这款游戏是一个死亡竞技场,各玩家分别控制一架可以射击和投弹的飞船。

我重点解释P状态的通信和同步。为了简化,我尽量把游戏和网络代码抽取出来。

注:命令式方法更不容易受到作弊行为的损害,因为服务器完全控制游戏状态,忽略任何可疑的信息,如一个客户端说自己移动了200象素而实际上只有10象素。

什么是“非命令式游戏”?

非命令式多人游戏没有控制游戏状态的中央实体(服务器),所以各个P都必须自己控制自己的游戏状态、交流任何变化和与其他P有关的动作。结果是,玩家会同时看到两个场面:他的飞船根据他的输入移动,和所有由其他玩家控制的飞船的模拟结果。

img3_simulation(from gamedev.tutsplus)

img3_simulation(from gamedev.tutsplus)

(玩家的飞船是本地控制的。对手飞船是根据网络通信模拟的。)

玩家的飞船移动和动作是由本地输入控制的,所以玩家的游戏状态几乎是立即更新的。在所有其他飞船移动时,玩家必须接收来自所有关于对手飞船当前位置的网络信息。

那些信息在电脑之间传输是需要时间的,所以当玩家收受告之对手飞船的位置信息时,对手飞船可能已经改变位置了—-这就是要先模拟的原因:

img4_lag(from-gamedev)

由网络导致的通信延迟(from-gamedev)

为了保持模拟的准确,各个P都只负责传播自己的飞船的信息,而不管其他飞船的信息。这意味着,如果游戏中有四名玩家A、B、C和D,玩家A只能告之飞船A的位置、它是否被击中、它是否发射或投弹,等等。所有其他玩家会接收到来自A的信息,然后做出相应的反应,所以如果A的子弹打到C的飞船,那么C只会把自己被打中的信息发给A。

所以,所有其他飞船只会根据自己接收到的信息做出行动。在理想的情况下,也就是没有网络延迟的情况下,信息传输会非常及时,模拟会极其精确。

然而,随着延迟增加,模拟会变得不准确。例如,玩家A射击,本地看到子弹打中B的飞船,但什么事也没发生;那是因为网络延迟。当B确实收到A的子弹发射信息时,B已经在另一个位置了,所以它不会被击中。

映射相关动作

执行游戏和保证所有玩家能够看到相同的准确的模拟的重要一步是,识别相关动作。那些动作会改变当前游戏状态,如从一个点移动到另一个点、投弹,等等。

在我们的游戏中,重要的动作是:

射击(玩这的飞船发射子弹或投炸弹)

移动(玩家的飞船移动)

死亡(玩家的飞船被摧毁)

img5_actions(from gamedev.tutsplus)

img5_actions(from gamedev.tutsplus)

(玩家飞船在游戏中的三种状态)

所有动作都必须在网络之间传送,所以必须在动作量和它们产生的网络信息量之间寻找平衡点。信息越大(也就是它包含的数据越多),它需要的传输时间就越长,因为它需要更多网络包。

简短的信息需要的CPU打包、发送和解压的时间更短。网络信息越小,相同时间内传送的信息就越多,这意味着吞吐率更高。

独立执行动作

映射相关动作后,就应该让它们在不需要玩家输入的情况下复写。即使那是优秀的软件开发的原则,但这一点对于多人游戏可能并不明显。

以游戏中的射击动作为例,如果它与那种输入逻辑有深刻的联系,那么在不同的情形下,游戏就不可能再次使用相同的射击代码:

img6_decoupled(from gamedev.tutsplus)

独立执行动作(from gamedev.tutsplus)

当射击代码与输入逻辑不挂钩时,游戏就可以使用相同的代码来执行玩家的射击和对手的射击(当这种网络信息到达时)。这样就避免代码重复和节省工作量了。

在我们的游戏中,飞船类没有多人代码;它只描述飞船是本地的或非本地的。然而,这个类有数种操作飞船的办法,如旋转()和改变位置。所以,多人代码可以用玩家输入代码一样的方式旋转飞船—-区别是,一个是根据本地输入,而另一个是根据网络信息民。

根据动作交换数据

现在,所有相关动作都映射好了,可以在不同P之间交换信息,以生成模拟。在交换数据以前,通信协议必须格式化。对于多人游戏通信,协议可以定义为一系列描述信息如何构建以便所有人可以发送、读取和理解信息的规则。

游戏中的交换的信息就是一个对象,包含一个强制属性即op(操作码)。这个op用于识别信息类型和表明这个信息对象具有的属性。以下是所有信息的结构:

img7_protocol(from gamedev.tutsplus)

网络信息的结构(from gamedev.tutsplus)

OP_DIE表示飞船被摧毁。它的x和y属性表示飞船被摧毁时所处的位置。

OP_POSITION表示飞船的当前位置。它的x和y属性表示飞船在屏幕上的坐标,angle表示飞船当旋转角度。

OP_SHOT表示飞船朝某飞船射击或投弹。它的x和y属性表示飞船射击时所处的位置;dx和dy属性表示飞船的方向,这保证其他P会使用相同的角度 模拟射击的飞船。b属性表示射击物的类型(子弹或炸弹)。

Multiplayer类

为了组织多玩家代码,我们创建了一个Multiplayer类。它负责发送和接收信息,以及根据接收到的反映游戏模拟的当前状态的信息来更新本地飞船。

它的初始结构,只包含一条信息代码,即:

public class Multiplayer
{
public const OP_SHOT        :String = “S”;
public const OP_DIE         :String = “D”;
public const OP_POSITION    :String = “P”;

public function Multiplayer() {
// Connection code was omitted.
}

public function sendObject(obj :Object) :void   {
// Network code used to send the object was omitted.
}
}

发送动作信息

为提前映射所有相关动作,网络信息必须发送,这样才能让所有P知道那个动作是什么。

当玩家被子弹或炸弹命中时,OP_DIE动作应该发送。游戏代码中已经有一个当飞船被击中而摧毁的方法了,所以游戏更新,以传送那个信息:

public function onPlayerHitByBullet() :void {
// Destoy player’s ship
playerShip.kill();

// MULTIPLAYER:
// Send a message to all other players informing
// the ship was destroyed.
multiplayer.sendObject({op: Multiplayer.OP_DIE, x: platerShip.x, y: playerShip.y});
}

每一次玩家改变飞船的当前位置,OP_POSITION动作就要发送一次。在游戏代码中添加multiplayer代码来传播那个信息:

public function updatePlayerInput():void {
var moved :Boolean = false;

if (wasMoveKeysPressed()) {
playerShip.x += playerShip.direction.x;
playerShip.y += playerShip.direction.y;

moved = true;
}

if (wasRotateKeysPressed()) {
playerShip.rotate(10);
moved = true;
}

// MULTIPLAYER:
// If player moved (or rotated), propagate the information.
if (moved) {
multiplayer.sendObject({op: Multiplayer.OP_POSITION, x: playerShip.x, y: playerShip.y, angle: playerShip.angle});
}
}

最后,每一次玩家飞船射击,OP_SHOT都要发送一次。这个信息包含发射物类型,这样所有P就会看到正确的发射物:

if (wasShootingKeysPressed()) {
var bulletType :Class = getBulletType();
game.shoot(playerShip, bulletType);

// MULTIPLAYER:
// Inform all other players that we fired a projectile.
multiplayer.sendObject({op: Multiplayer.OP_SHOT, x: playerShip.x, y: playerShip.y, dx: playerShip.direction.x, dy: playerShip.direction.y, b: bBulletType)});
}

根据接收到的数据同步状态

此时,各名玩家都能够控制和看到自己的飞船。网络信息根据相关动作发送。唯一缺少的就是对手,所以各名玩家可以看到其他飞船并与它们产生交互作用。

在游戏中,飞船被组织成数组。这个数组目前只有一个飞船(玩家)。为了产生其他玩家的模拟,当有新玩家加入战场时,Multiplayer类就会增加新飞船到那个数组中:

public class Multiplayer
{
public const OP_SHOT        :String = “S”;
public const OP_DIE         :String = “D”;
public const OP_POSITION    :String = “P”;

(…)

// This method is invoked every time a new user joins the arena.
protected function handleUserAdded(user :UserObject) :void {
// Create a new ship base on the new user’s id.
var ship :Ship = new ship(user.id);
// Add the ship the to array of already existing ships.
game.ships.add(ship);
}
}

这个信息交换代码自动为所有玩家提供唯一识别符(也就是上述代码中的user.id)。当玩家加入战场时,multiplayer代码就会使用的这种识别法产生新飞船;这样,所有飞船都是唯一的识别符。使用所有接受到的信息的作家识别符,就可以在飞船数组中看到那个飞船。

最后,可以添加handleGetObject()到Multiplayer类中了。这个方法是调用新信息到达的时间:

public class Multiplayer
{
public const OP_SHOT        :String = “S”;
public const OP_DIE         :String = “D”;
public const OP_POSITION    :String = “P”;

(…)

// This method is invoked every time a new user joins the arena.
protected function handleUserAdded(user :UserObject) :void {
// Create a new ship base on the new user’s id.
var ship :Ship = new ship(user.id);
// Add the ship the to array of already existing ships.
game.ships.add(ship);
}

protected function handleGetObject(userId :String, data :Object) :void {
var opCode :String = data.op;

// Find the ship of the player who sent the message
var ship :Ship = getShipById(userId);

switch(opCode) {
case OP_POSITION:
// Message to update the author’s ship position.
ship.x  = data.x;
ship.y  = data.y;
ship.angle  = data.angle;
break;

case OP_SHOT:
// Message informing the author’ ship fired a projecle.
// First of all, update the ship position and direction.
ship.x  = data.x;
ship.y  = data.y;
ship.direction.x = data.dx;
ship.direction.y = data.dy;

// Fire the projectile from the author’s ship location.
game.shoot(ship, data.b);
break;

case OP_DIE:
// Message informing the author’s ship was destroyed.
ship.kill();
break;
}
}
}

当新信息到达时,handleGetObject()方法调用两个参数:author ID(唯一识别符)和信息数据。分析信息数据,据此,要提取操作代码和所有其他属性。

使用提取后的数据,multiplayer代码重新产生所有通过网络接收到的动作。以OP_SHOT信息为例,更新当关游戏状态有以下几步:

1、查看本地飞船的userId

2、根据接收到的信息更新飞船的位置和角度

3、根据接收到的信息更新飞船的方向

4、调用负责射击属性的游戏方法,发射子弹或炸弹

正如之前描述的,射击代码与玩家和输入逻辑不挂钩,所以这个射击动作表现得与玩家本地射击完全一样。

缓解延迟问题

如果游戏只根据网络更新来移动实体,那么任何丢失的或延迟的信息都会导致实体从一个位置“超时空转换”到另一个位置。我们可以用本地预测来缓和这个问题。

例如使用插值法,实体移动是在本地用一个点替换另一个点(都被网络更新接收)。结果,实体会流畅地在这些点之间移动。理想情况下,延迟应该不会超过实体从一点替换到另一点所需的时间。

另一个技巧是外推法,也就是根据实体的当前状态本地移动它。假设实体不会改变它的当前路径,那么就可以根据它的当前方向和速度移动。如果延迟不太严重,外推法就可以准确地重新产生预期的实体移动,直到新的网络更新到达,这样移动也会是流畅的。

尽管有了这些技巧,有时候网络延迟还是非常严重,无法控制。早期解决这个问题的办法是断开有问题的P。比较安全的办法是暂停游戏:如果P在一定的时间内不响应,就断开它的连接。

总结

执行多人联网游戏是一件有难度又令人兴奋的任务。它需要你兼顾不同的事,因为所有P都要发送和重新产生所有相关的动作。这样,所有玩家才能及时地看到除了不会延迟的本地飞船外,其他飞船的模拟情况。

本教程描述了如何使用非命令式P2P方法执行多人游戏。以上所有概念都可以拓展到不同的多人机制中。(本文为游戏邦/gamerboom.com编译,拒绝任何不保留版权的转载,如需转载请联系:游戏邦

Building a Peer-to-Peer Multiplayer Networked Game

by Fernando Bevilacqua

Playing a multiplayer game is always fun. Instead of beating AI-controlled opponents, the player must face strategies created by another human being. This tutorial presents the implementation of a multiplayer game played over the network using a non-authoritative peer-to-peer (P2P) approach.

Note: Although this tutorial is written using AS3 and Flash, you should be able to use the same techniques and concepts in almost any game development environment. You must have a basic understanding of networking communication.

You can download or fork the final code from the GitHub repo or the zipped source files.

Final Result Preview

Network demo. Controls: arrows or WASD to move, Space to shoot, B to deploy a bomb.

Art from Remastered Tyrian Graphics, Iron Plague and Hard Vacuum by Daniel Cook (Lost Garden).

Introduction

A multiplayer game played over the network can be implemented using several different approaches, which can be categorized into two groups: authoritative and non-authoritative.

In the authoritative group, the most common approach is the client-server architecture, where a central entity (the authoritative server) controls the whole game. Every client connected to the server constantly receives data, locally creating a representation of the game state. It’s a bit like watching TV.

Authoritative implementation using client-server architecture.

If a client performs an action, such as moving from one point to another, that information is sent to the server. The server checks whether the information is correct, then updates its game state. After that it propagates the information to all clients, so they can update their game state accordingly.

In the non-authoritative group, there is no central entity and every peer (game) controls its game state. In a peer-to-peer (P2P) approach, a peer sends data to all other peers and receives data from them, assuming that information is reliable and correct (cheating-free):

Non-authoritative implementation using P2P architecture.

In this tutorial I present the implementation of a multiplayer game played over the network using a non-authoritative P2P approach. The game is a deathmatch arena where each player controls a ship able to shoot and drop bombs.

I’m going to focus on the communication and synchronization of peer states. The game and the networking code are abstracted as much as possible for the sake of simplification.

Tip: the authoritative approach is more secure against cheating, because the server fully controls the game state and can ignore any suspicious message, like an entity saying it moved 200 pixels when it could only have moved 10.

Defining a Non-Authoritative Game

A non-authoritative multiplayer game has no central entity to control the game state, so every peer must control its own game state, communicating any changes and important actions to the others. As a consequence, the player sees two scenarios simultaneously: his ship moving according to his input and a simulation of all other ships controlled by the opponents:

Player’s ship is controlled locally. Opponent ships are simulated based on network communication.

The player’s ship’s movement and actions are guided by local input, so the player’s game state is updated almost instantly. For the movement of all the other ships, the player must receive a network message from every opponent informing where their ships are.

Those messages take time to travel over the network from one computer to another, so when the player receives an information saying an opponent’s ship is at (x, y), it’s probably not there any more – that’s why it’s a simulation:

Communication delay caused by the network.

In order to keep the simulation accurate, every peer is responsible for propagating only the information about its ship, not the others. This means that, if the game has four players – say A, B, C and D – player A is the only one able to inform where ship A is, if it got hit, if it fired a bullet or dropped a bomb, and so on.  All other players will receive messages from A informing about his actions and they will react accordingly, so if A’s bullet got C’s ship, then C will broadcast a message informing it was destroyed.

As a consequence, each player will see all other ships (and their actions) according to the received messages. In a perfect world, there would be no network latency, so messages would come and go instantly and the simulation would be extremely accurate.

As the latency increases, however, the simulation becomes inaccurate. For example, player A shoots and locally sees the bullet hitting B‘s ship, but nothing happens; that’s because A‘s view of B is delayed due to network lag. When B actually received A‘s bullet message, B was at a different position, so no hit was propagated.

Mapping Relevant Actions

An important step in implementing the game and ensuring that every player will be able to see the same simulation accurately is the identification of relevant actions. Those actions change the current game state, such as moving from one point to another, dropping a bomb, etc.

In our game, the important actions are:

shoot (player’s ship fired a bullet or a bomb)
move (player’s ship moved)
die (player’s ship was destroyed)

Player actions during the game.

Every action must be sent over the network, so it’s important to find a balance between the amount of actions and the size of the network messages they will generate. The bigger the message is (that is, the more data it contains), the longer it will take to be transported, because it might need more than one network package.

Short messages demand fewer CPU time to pack, send, and unpack. Small network messages also result in more messages being sent at the same time, which increases the throughput.

Performing Actions Independently

After the relevant actions are mapped, it’s time to make them reproducible without user input. Even though that’s a principle of good software engineering, it might not be obvious from a multiplayer game point of view.
Using the shooting action of our game as an example, if it’s deeply interconnected with the input logic, it’s not possible to re-use that same shooting code in different situations:

Performing actions independently.

When the shooting code is decoupled from the input logic, for instance, it’s possible to use the same code to shoot the player’s bullets and the opponent’s bullets (when such a network message arrives). It avoids code replication and prevents a lot of headache.

The Ship class in our game, for instance, has no multiplayer code; it is completely decoupled. It describes a ship, be it local or not. The class, however, has several methods for manipulating the ship, such as rotate() and a setter for changing its position. As a consequence, the multiplayer code can rotate a ship the same way the user input code does – the difference is that one is based on local input, while the other is based on network messages.

Exchanging Data Based on Actions

Now that all relevant actions are mapped, it’s time to exchange messages among the peers to create the simulation. Before exchanging any data, a communication protocol must be formulated. Regarding a multiplayer game communication, a protocol can be defined as a set of rules that describe how a message is structured, so everyone can send, read, and understand those messages.

The messages exchanged in the game will be described as objects, all containing a mandatory property called op (operation code). The op is used to identify the message type and indicate the properties the message object has. This is the structure of all messages:

tructure of network messages.

The OP_DIE message states that a ship was destroyed. Its x and y properties contain the ship’s location when it was destroyed.

The OP_POSITION message contains the current location of a peer’s ship. Its x and y properties contain the ship’s coordinates on the screen, while angle is the ship’s current rotation angle.

The OP_SHOT message states that a ship fired something (a bullet or a bomb). The x and y properties contain the ship’s location when it fired; the dx and dy properties indicate the ship direction, which ensures the bullet will be replicated in all peers using the same angle the firing ship used when it was aiming; and the b property defines the projectile’s type (bullet or bomb).

The Multiplayer Class

In order to organize the multiplayer code, we create a Multiplayer class. It is responsible for sending and receiving messages, as well as updating the local ships according to the received messages to reflect the current state of the game simulation.

Its initial structure, containing only the message code, is:

public class Multiplayer
{
public const OP_SHOT        :String = “S”;
public const OP_DIE         :String = “D”;
public const OP_POSITION    :String = “P”;

public function Multiplayer() {
// Connection code was omitted.
}

public function sendObject(obj :Object) :void   {
// Network code used to send the object was omitted.
}
}

Sending Action Messages

For every relevant action mapped previously, a network message must be sent, so all peers will be informed about that action.

The OP_DIE action should be sent when the player is hit by a bullet or a bomb explosion. There is already a method in the game code that destroys the player ship when it is hit, so it’s updated to propagate that information:

public function onPlayerHitByBullet() :void {
// Destoy player’s ship
playerShip.kill();

// MULTIPLAYER:
// Send a message to all other players informing
// the ship was destroyed.
multiplayer.sendObject({op: Multiplayer.OP_DIE, x: platerShip.x, y: playerShip.y});
}

The OP_POSITION action should be sent every time the player changes its current position. The multiplayer code is injected into the game code to propagate that information, too:

public function updatePlayerInput():void {
var moved :Boolean = false;

if (wasMoveKeysPressed()) {
playerShip.x += playerShip.direction.x;
playerShip.y += playerShip.direction.y;

moved = true;
}

if (wasRotateKeysPressed()) {
playerShip.rotate(10);
moved = true;
}

// MULTIPLAYER:
// If player moved (or rotated), propagate the information.
if (moved) {
multiplayer.sendObject({op: Multiplayer.OP_POSITION, x: playerShip.x, y: playerShip.y, angle: playerShip.angle});
}
}

Finally, the OP_SHOT action must be sent every time the player fires something. The sent message contains the bullet type that was fired, so that every peer will see the correct projectile:

if (wasShootingKeysPressed()) {
var bulletType :Class = getBulletType();
game.shoot(playerShip, bulletType);

// MULTIPLAYER:
// Inform all other players that we fired a projectile.
multiplayer.sendObject({op: Multiplayer.OP_SHOT, x: playerShip.x, y: playerShip.y, dx: playerShip.direction.x, dy: playerShip.direction.y, b: bBulletType)});
}

Synchronizing Based on Received Data

At this point, each player is able to control and see their ship. Under the hood, the network messages are sent based on relevant actions. The only missing piece is the addition of the opponents, so that each player can see the other ships and interact with them.

In the game, the ships are organized as an array. That array had just a single ship (the player) until now. In order to create the simulation for all other players, the Multiplayer class will be changed to add a new ship to that array whenever a new player joins the arena:

public class Multiplayer
{
public const OP_SHOT        :String = “S”;
public const OP_DIE         :String = “D”;
public const OP_POSITION    :String = “P”;

(…)

// This method is invoked every time a new user joins the arena.
protected function handleUserAdded(user :UserObject) :void {
// Create a new ship base on the new user’s id.
var ship :Ship = new ship(user.id);
// Add the ship the to array of already existing ships.
game.ships.add(ship);
}
}

The message exchanging code automatically provides a unique identifier for every player (the user.id in the code above). That identification is used by the multiplayer code to create a new ship when a player joins the arena; this way, every ship has a unique identifier. Using the author identifier of every received message, it’s possible to look up that ship in the array of ships.

Finally, it’s time to add the handleGetObject() to the Multiplayer class. This method is invoked every time a new message arrives:

public class Multiplayer
{
public const OP_SHOT        :String = “S”;
public const OP_DIE         :String = “D”;
public const OP_POSITION    :String = “P”;

(…)

// This method is invoked every time a new user joins the arena.
protected function handleUserAdded(user :UserObject) :void {
// Create a new ship base on the new user’s id.
var ship :Ship = new ship(user.id);
// Add the ship the to array of already existing ships.
game.ships.add(ship);
}

protected function handleGetObject(userId :String, data :Object) :void {
var opCode :String = data.op;

// Find the ship of the player who sent the message
var ship :Ship = getShipById(userId);

switch(opCode) {
case OP_POSITION:
// Message to update the author’s ship position.
ship.x  = data.x;
ship.y  = data.y;
ship.angle  = data.angle;
break;

case OP_SHOT:
// Message informing the author’ ship fired a projecle.
// First of all, update the ship position and direction.
ship.x  = data.x;
ship.y  = data.y;
ship.direction.x = data.dx;
ship.direction.y = data.dy;

// Fire the projectile from the author’s ship location.
game.shoot(ship, data.b);
break;

case OP_DIE:
// Message informing the author’s ship was destroyed.
ship.kill();
break;
}
}
}

When a new message arrives, the handleGetObject() method is invoked with two parameters: the author ID (unique identifier) and the message data. Analyzing the message data, the operation code is extracted and, based on that, all other properties are extracted as well.

Using the extracted data, the multiplayer code reproduces all actions that were received over the network. Taking the OP_SHOT message as an example, these are the steps performed to update the current game state:

Look up the local ship identified by userId.

Update Ship‘s position and angle according to received data.

Update Ship‘s direction according to received data.

Invoke the game method responsible for firing projectiles, firing  a bullet or a bomb.

As previously described, the shooting code is decoupled from the player and the input logic, so the projectile fired behaves exactly like one fired by the player locally.

Mitigating Latency Issues

If the game exclusively moves entities based on network updates, any lost or delayed message will cause the entity to “teleport” from one point to another. That can be mitigated with local predictions.

Using  interpolation, for instance, the entity movement is locally interpolated from one point to another (both received by network updates). As a result, the entity will smoothly move between those points. Ideally, the latency should not exceed the time an entity takes to be interpolated from one point to another.

Another trick is extrapolation, which locally moves entities based on its current state. It assumes that the entity will not change its current route, so it’s safe to make it move according to its current direction and velocity, for instance. If the latency is not too high, the extrapolation accurately reproduces the entity expected movement until a new network update arrives, resulting in a smooth movement pattern.

Despite those tricks, the network latency can be extremely high and unmanageable sometimes. The easiest approach to eliminate that is to disconnect the problematic peers. A safe approach for that is to use a timeout: if the peer takes more than an specified time to answer, it is disconnected.

Conclusion

Making a multiplayer game played over the network is a challenging and exciting task. It requires a different way of seeing things since all relevant actions must be sent and reproduced by all peers. As a consequence, all players see a simulation of what is happening, except for the local ship, which has no network latency.
This tutorial described the implementation of a multiplayer game using a non-authoritative P2P approach. All the concepts presented can be expanded to implement different multiplayer mechanics. Let the multiplayer game making begin!(source:gamedev.tutsplus)


上一篇:

下一篇: