在浏览器中创建一个多人海盗射击游戏:

制作多人游戏具有挑战性,原因有以下几个:托管成本昂贵,设计棘手且难以实施。 通过本教程,我希望解决最后一个障碍。

这是针对那些知道如何制作游戏并熟悉JavaScript但从未制作过在线多人游戏的开发人员的。 完成后,您应该很容易在任何游戏中实现基本的网络组件,并能够在此基础上进行构建!

这是我们将要构建的:

最终游戏的屏幕截图-两艘船互相攻击

您可以在此处尝试游戏的实时版本 ! W或上移至鼠标并单击拍摄。 (如果没有其他人在线,请尝试在同一台计算机上打开两个浏览器窗口,或者在您的手机上打开一个窗口,以观察多人游戏的工作原理)。 如果您有兴趣在本地运行它,则完整的源代码也可以在GitHub找到

使用Kenney的Pirate Pack艺术资产和Phaser游戏框架将游戏整合在一起。 在本教程中,您将扮演网络程序员的角色。 您的起点将是该游戏的一个功能全面的单人游戏版本,并且将使用Socket.io作为网络部分在Node.js中编写服务器将是您的工作。 为了使本教程易于管理,我将专注于多人游戏部分,并简要介绍Phaser和Node.js的特定概念。

无需在本地进行任何设置,因为我们将完全在Glitch.com的浏览器中制作此游戏! Glitch是一个很棒的工具,用于构建Web应用程序,包括后端,数据库以及所有内容。 它非常适合原型,教学和协作,我很高兴在本教程中向您介绍它。

让我们潜入。

1.设定

我已经在Glitch.com上安装了入门工具包。

一些快速的界面提示:在任何时候,您都可以通过单击“ 显示”按钮(左上角)来查看应用程序的实时预览。

显示按钮在故障界面的左上方

左侧的垂直边栏包含应用程序中的所有文件。 要编辑此应用,您需要对其进行“重新混合”。 这将在您的帐户上创建一个副本(或在git lingo中将其派生)。 单击重新混合此按钮。

混音按钮在代码编辑器的顶部

此时,您将使用匿名帐户编辑该应用程序。 您可以登录(右上角)以保存您的工作。

现在,在进行下一步之前,熟悉要添加多人游戏的代码很重要。请看一下index.html 。 除了播放器对象(第35行)之外,还有三个重要的功能需要注意: preload (第99行), create (第115行)和GameLoop (第142行)。

如果您想边做边学,请尝试以下挑战,以确保您了解游戏的工作原理:

  • 扩大世界(第29行)请注意,页面上的游戏内世界有一个单独的世界大小,而实际画布上有一个窗口大小
  • 使SPACEBAR也向前推(第53行)。
  • 更改您的玩家飞船类型(第129行)。
  • 使子弹移动变慢(第155行)。

安装Socket.io

Socket.io是一个用于使用WebSockets在浏览器中管理实时通信的库(如果要构建多人桌面游戏,则使用UDP等协议)。 它也具有后备功能,以确保即使不支持WebSocket仍能正常工作。 因此,它可以处理消息传递协议,并提供一个不错的基于事件的消息系统供您使用。

我们需要做的第一件事是安装Socket.io模块。 在Glitch上,您可以执行以下操作:转到package.json文件,然后在依赖项中键入所需的模块,或者单击“ 添加软件包”并键入“ socket.io”

选择文件packagejson时,可在代码编辑器顶部找到添加软件包菜单。

这是指出服务器日志的好时机。 单击左侧的日志按钮以显示服务器日志。 您应该看到它安装了Socket.io及其所有依赖项。 您可以在这里查看服务器代码的任何错误或输出。

日志按钮在屏幕的左侧

现在去server.js 。 这是服务器代码所在的位置。 现在,它只是用于提供HTML的一些基本样板。 在顶部添加以下行以包含Socket.io:

var io = require('socket.io')(http); // Make sure to put this after http has been defined

现在我们还需要在客户端上包含Socket.io,因此请返回index.html并将其添加到<head>标记的顶部:

<!-- Load the Socket.io networking library -->
<script src="/socket.io/socket.io.js"></script>

注意:Socket.io自动处理在该路径上提供客户端库的功能,因此即使在文件夹中看不到目录/socket.io/,这行也可以工作。

现在已包含Socket.io并准备开始!

2.检测并产生玩家

我们真正的第一步将是接受服务器上的连接,并在客户端上产生新的播放器。

接受服务器上的连接

server.js的底部,添加以下代码:

// Tell Socket.io to start accepting connections
io.on('connection', function(socket){
    console.log("New client has connected with id:",socket.id);
})

这告诉Socket.io侦听任何connection事件,当客户端连接时会自动触发该事件。 它将为每个客户端创建一个新的socket对象,其中socket.id是该客户端的唯一标识符。

为了确保此方法有效,请返回到客户端( index.html )并将此行添加到create函数中的某处:

var socket = io(); // This triggers the 'connection' event on the server

如果启动游戏,然后查看服务器日志(单击“ 日志”按钮),则应该看到它记录了该连接事件!

现在,当新玩家连接时,我们希望他们向我们发送有关其状态的信息。 在这种情况下,我们至少需要知道xy角度 ,以便在正确的位置正确生成它们。

事件connection是Socket.io为我们触发的内置事件。 我们可以监听我们想要的任何自定义事件。 我要称呼我的new-player ,我希望客户端在与他们的位置信息相关联后立即将其发送。 看起来像这样:

// Tell Socket.io to start accepting connections
io.on('connection', function(socket){
    console.log("New client has connected with id:",socket.id);
    socket.on('new-player',function(state_data){ // Listen for new-player event on this client 
      console.log("New player has state:",state_data);
    })
})

如果运行此命令,服务器日志中将看不到任何内容。 这是因为我们尚未告知客户端发出此new-player事件。 但是,让我们假装已经解决了片刻,然后继续在服务器上运行。 收到新加入玩家的位置后,应该怎么办?

我们可以向其他所有连接的玩家发送消息,让他们知道有新玩家加入。 Socket.io提供了一个方便的功能来执行此操作:

socket.broadcast.emit('create-player',state_data);

调用socket.emit只会将消息发送回该客户端。 调用socket.broadcast.emit将其发送到连接到服务器的每个客户端,除了调用了一个套接字。

使用io.emit会将消息io.emit发送到连接到服务器的每个客户端。 我们不希望在当前设置下执行此操作,因为如果从服务器返回一条消息,要求您创建自己的飞船,则会有一个重复的精灵,因为我们已经在游戏开始时创建了自己的玩家的飞船。 这是我们在本教程中将使用的各种消息传递功能的便捷备忘单

服务器代码现在应如下所示:

// Tell Socket.io to start accepting connections
io.on('connection', function(socket){
    console.log("New client has connected with id:",socket.id);
    socket.on('new-player',function(state_data){ // Listen for new-player event on this client 
      console.log("New player has state:",state_data);
      socket.broadcast.emit('create-player',state_data);
    })
})

因此,每次玩家连接时,我们希望他们将包含其位置数据的消息发送给我们,并且我们会将数据立即发送回其他所有玩家,以便他们可以生成该精灵。

在客户端产生

现在,要完成此循环,我们知道我们需要在客户端上做两件事:

  1. 建立连接后,发送一条包含我们位置数据的消息。
  2. 侦听create-player事件,并在该位置生成玩家。

对于第一个任务,在创建函数中创建播放器后(第135行左右),我们可以发出一条包含要发送的位置数据的消息,如下所示:

socket.emit('new-player',{x:player.sprite.x,y:player.sprite.y,angle:player.sprite.rotation})

您不必担心序列化发送的数据。 您可以传入任何对象,然后Socket.io会为您处理。

在继续前进之前, 测试这可行 。 您应该在服务器日志上看到一条消息,内容如下:

New player has state: { x: 728.8180247836519, y: 261.9979387913289, angle: 0 }

我们知道我们的服务器收到了有关新玩家已连接的公告,并正确获取了他们的位置数据!

接下来,我们要侦听创建新播放器的请求。 我们可以在发出代码后立即放置此代码,其外观应类似于:

socket.on('create-player',function(state){
  // CreateShip is a function I've already defined to create and return a sprite 
  CreateShip(1,state.x,state.y,state.angle)
})

现在测试一下 。 打开游戏的两个窗口,看看它是否有效。

您应该看到的是,在打开两个客户端之后,第一个客户端将有两艘生成的飞船,而第二个客户端只会看到一艘。

挑战:您能弄清楚为什么会这样吗? 或者您如何解决? 逐步完成我们编写的客户端/服务器逻辑,然后尝试对其进行调试。

希望您有机会自己考虑一下! 发生的情况是,当第一个玩家连接时,服务器向每个其他玩家发送了一个create-player事件,但是周围没有其他玩家可以接收它。 一旦第二个播放器连接,服务器再次发送其广播,播放器1接收它并正确生成精灵,而播放器2错过了播放器1的初始连接广播。

因此出现此问题是因为玩家2在游戏后期加入,并且需要知道游戏的状态。 我们需要告诉正在连接的任何新玩家,哪些玩家已经存在(或世界上已经发生了什么),以便他们赶上。 在我们着手解决此问题之前,我有个简短的警告。

关于同步游戏状态的警告

有两种方法可以使每个玩家的游戏保持同步。 第一个是仅发送有关网络上已更改内容的最少信息。 因此,每次有新玩家连接时,您只需将该新玩家的信息发送给所有其他玩家(并向该新玩家发送世界上所有其他玩家的列表),当他们断开连接时,您便会告诉所有其他玩家该个人客户端已断开连接。

第二种方法是发送整个游戏状态。 在这种情况下,每次连接或断开连接时,您只需将所有玩家的完整列表发送给所有人。

第一个是更好的,因为它可以最大程度地减少通过网络发送的信息,但是它可能非常棘手,并且存在玩家不同步的风险。 第二个保证播放器将始终保持同步,但涉及在每条消息中发送更多数据。

在我们的案例中,我们可以尝试将所有玩家合并为一个update事件,而不是尝试在连接了新玩家时创建消息,断开连接以删除它们以及移动它们以更新位置时发送消息。 。 此更新事件将始终将每个可用玩家的位置发送给所有客户端。 这就是服务器要做的所有事情。 然后,客户负责使其世界保持最新状态。

为了实现这一点,我将:

  1. 保留一个玩家字典,其中的键是他们的ID,值是他们的位置数据。
  2. 当他们连接并发送更新事件时,将播放器添加到此词典中。
  3. 当他们断开连接并发送更新事件时,请从此词典中删除播放器。

您可以尝试自己实施此步骤,因为这些步骤非常简单( 备忘单可能会派上用场)。 完整的实现如下所示:

// Tell Socket.io to start accepting connections
// 1 - Keep a dictionary of all the players as key/value 
var players = {};
io.on('connection', function(socket){
    console.log("New client has connected with id:",socket.id);
    socket.on('new-player',function(state_data){ // Listen for new-player event on this client 
      console.log("New player has state:",state_data);
      // 2 - Add the new player to the dict
      players[socket.id] = state_data;
      // Send an update event
      io.emit('update-players',players);
    })
    socket.on('disconnect',function(){
      // 3- Delete from dict on disconnect
      delete players[socket.id];
      // Send an update event 
    })
})

客户端有点棘手。 一方面,我们只需要担心现在的update-players事件,但是另一方面,如果服务器向我们发送的舰船比我们所知道的多,那么我们就必须考虑创建更多的舰船,或者如果我们的舰船数量太多,则要销毁。

这是我在客户端上处理此事件的方式:

// Listen for other players connecting
// NOTE: You must have other_players = {} defined somewhere 
socket.on('update-players',function(players_data){
    var players_found = {};
    // Loop over all the player data received
    for(var id in players_data){
        // If the player hasn't been created yet
        if(other_players[id] == undefined && id != socket.id){ // Make sure you don't create yourself
            var data = players_data[id];
            var p = CreateShip(1,data.x,data.y,data.angle);
            other_players[id] = p;
            console.log("Created new player at (" + data.x + ", " + data.y + ")");
        }
        players_found[id] = true;
        
        // Update positions of other players 
        if(id != socket.id){
          other_players[id].x  = players_data[id].x; // Update target, not actual position, so we can interpolate
          other_players[id].y  = players_data[id].y;
          other_players[id].rotation  = players_data[id].angle;
        }
        
        
    }
    // Check if a player is missing and delete them 
    for(var id in other_players){
        if(!players_found[id]){
            other_players[id].destroy();
            delete other_players[id];
        }
    }
   
})

我正在一个名为other_players的字典中跟踪客户端上的other_players ,我只是在脚本的顶部定义了字典(此处未显示)。 由于服务器将玩家数据发送给所有玩家,因此我必须添加一张支票,以使客户端不会为自己创建额外的精灵。 (如果您在构造此代码时遇到麻烦, 这是此时应位于index.html的完整代码 )。

现在测试一下 。 您应该能够创建并关闭多个客户端,并在正确的位置看到正确数量的飞船!

3.同步船位

这是我们真正有趣的部分。 我们现在想立即在所有客户之间同步船的位置。 这是到目前为止我们构建的结构的简单性真正体现出来的地方。 我们已经有一个更新事件,可以同步每个人的位置。 我们现在需要做的是:

  1. 让客户在每次搬到新位置时都发出通知。
  2. 使服务器侦听该移动消息,并更新players词典中该players的条目。
  3. 向所有客户端发出更新事件。

就是这样! 现在该轮到您自己尝试实现了。

如果您完全陷入困境并需要提示,可以参考最终完成的项目作为参考。

有关最小化网络数据的注意事项

实现此目的最直接的方法是,每当您收到来自任何玩家的移动消息时,都用新的位置更新所有玩家。 这样做的好处是,播放器将始终在获得最新信息后立即接收最新信息,但是通过网络发送的消息数量很容易每帧增加到数百条。 想象一下,如果您有10个播放器,每个播放器都在每帧发送一个移动消息,然后服务器必须将其转发回所有10个播放器。 每帧已经有100条消息!

更好的方法可能是等到服务器收到播放器的所有消息后再向所有播放器发送包含所有信息的大更新。 这样,您可以将要发送的消息数压缩为仅与游戏中拥有的玩家数(而不是该数的平方)相对应。 然而,问题在于,每个人都会遇到与游戏中连接速度最慢的玩家一样多的延迟。

做到这一点的另一种方法是,简单地让服务器以恒定的速率发送更新,而不管到目前为止它从玩家那里收到了多少条消息。 以每秒30次左右的速度更新服务器似乎是一种常见的标准。

但是,无论您决定构建服务器,请注意在开发游戏时每帧要发送多少消息。

4.同步项目符号

我们快到了! 最后一件大事将是在网络上同步项目符号。 我们可以采用同步播放器的相同方式进行操作:

  • 每个客户每帧发送所有子弹的位置。
  • 服务器将其转发给每个玩家。

但有一个问题。

防止作弊

如果您中继客户端发送给您的任何内容作为子弹的真实位置,则玩家可以通过修改其客户端以向您发送虚假数据(例如,将子弹传送到其他船只所在的位置)作弊。 您可以通过下载网页,修改JavaScript并再次运行来轻松地自己尝试一下。 对于浏览器开发的游戏来说,这不仅仅是一个问题。 通常,您永远无法真正信任来自客户端的数据。

为了减轻这种情况,我们将尝试其他方案:

  • 只要客户发射了带有位置和方向的子弹,客户端就会发出。
  • 服务器模拟子弹的运动。
  • 服务器用所有项目符号的位置更新每个客户端。
  • 客户端在服务器接收的位置提供项目符号。

这样,客户可以控制子弹的产生位置,而不是子弹的移动速度或之后的位置。 客户可以在自己的视图中更改项目符号的位置,但他们不能更改其他客户看到的内容。

现在,要实现这一点,在拍摄时我将添加一个发射。 我也不再创建实际的精灵,因为它的存在和位置现在完全由服务器确定。 现在, index.html中的新子弹射击代码应如下所示:

// Shoot bullet 
if(game.input.activePointer.leftButton.isDown && !this.shot){
    var speed_x = Math.cos(this.sprite.rotation + Math.PI/2) * 20;
    var speed_y = Math.sin(this.sprite.rotation + Math.PI/2) * 20;
    /* The server is now simulating the bullets, clients are just rendering bullet locations, so no need to do this anymore
    var bullet = {};
    bullet.speed_x = speed_x;
    bullet.speed_y = speed_y;
    bullet.sprite = game.add.sprite(this.sprite.x + bullet.speed_x,this.sprite.y + bullet.speed_y,'bullet');
    bullet_array.push(bullet); 
    */
    this.shot = true;
    // Tell the server we shot a bullet 
    socket.emit('shoot-bullet',{x:this.sprite.x,y:this.sprite.y,angle:this.sprite.rotation,speed_x:speed_x,speed_y:speed_y})
}

现在,您也可以注释掉整个部分,以更新客户端上的项目符号:

/* We're updating the bullets on the server, so we don't need to do this on the client anymore 
// Update bullets 
for(var i=0;i<bullet_array.length;i++){
    var bullet = bullet_array[i];
    bullet.sprite.x += bullet.speed_x; 
    bullet.sprite.y += bullet.speed_y; 
    // Remove if it goes too far off screen 
    if(bullet.sprite.x < -10 || bullet.sprite.x > WORLD_SIZE.w || bullet.sprite.y < -10 || bullet.sprite.y > WORLD_SIZE.h){
        bullet.sprite.destroy();
        bullet_array.splice(i,1);
        i--;
    }
} 
*/

最后,我们需要让客户端监听项目符号更新。 我选择以与播放器相同的方式进行处理,在这种情况下,服务器仅在称为bullets-update的事件中发送所有子弹位置的数组,客户端将创建或销毁子弹以保持同步。 看起来像这样:

// Listen for bullet update events 
socket.on('bullets-update',function(server_bullet_array){
  // If there's not enough bullets on the client, create them
 for(var i=0;i<server_bullet_array.length;i++){
      if(bullet_array[i] == undefined){
          bullet_array[i] = game.add.sprite(server_bullet_array[i].x,server_bullet_array[i].y,'bullet');
      } else {
          //Otherwise, just update it! 
          bullet_array[i].x = server_bullet_array[i].x; 
          bullet_array[i].y = server_bullet_array[i].y;
      }
  }
  // Otherwise if there's too many, delete the extra 
  for(var i=server_bullet_array.length;i<bullet_array.length;i++){
       bullet_array[i].destroy();
       bullet_array.splice(i,1);
       i--;
   }
                  
                })

那应该是客户端上的所有内容。 我假设您现在知道这些片段的位置以及如何将所有片段组合在一起,但是如果遇到任何问题,请记住您可以随时查看最终结果以供参考。

现在,在server.js上,我们需要跟踪并模拟项目符号。 首先,我们创建一个用于跟踪子弹的数组,就像我们为玩家提供一个数组一样:

var bullet_array = []; // Keeps track of all the bullets to update them on the server

接下来,我们收听射击子弹事件:

// Listen for shoot-bullet events and add it to our bullet array
  socket.on('shoot-bullet',function(data){
    if(players[socket.id] == undefined) return;
    var new_bullet = data;
    data.owner_id = socket.id; // Attach id of the player to the bullet 
    bullet_array.push(new_bullet);
  });

现在,我们每秒模拟子弹60次:

// Update the bullets 60 times per frame and send updates 
function ServerGameLoop(){
  for(var i=0;i<bullet_array.length;i++){
    var bullet = bullet_array[i];
    bullet.x += bullet.speed_x; 
    bullet.y += bullet.speed_y; 
    
    // Remove if it goes too far off screen 
    if(bullet.x < -10 || bullet.x > 1000 || bullet.y < -10 || bullet.y > 1000){
        bullet_array.splice(i,1);
        i--;
    }
        
  }
  
}

setInterval(ServerGameLoop, 16);

最后一步是将update事件发送到该函数内部的某个位置(但肯定在for循环之外):

// Tell everyone where all the bullets are by sending the whole array
  io.emit("bullets-update",bullet_array);

现在您可以进行实际测试了! 如果一切顺利,您应该会看到项目符号在客户端之间正确同步。 我们在服务器上执行此操作的事实是更多的工作,但同时也使我们有了更多的控制权。 例如,当我们收到射击子弹事件时,我们可以检查子弹的速度是否在一定范围内,否则我们知道该玩家在作弊。

5.子弹碰撞

这是我们将要实现的最后一个核心机制。 希望到目前为止,您已经习惯了规划实现的过程,先完全完成客户端的实现,然后再转到服务器(反之亦然)。 与实现时来回切换相比,这是一种不那么容易出错的方式。

检查碰撞是至关重要的游戏机制,因此我们希望它能防欺诈。 我们将以在项目符号中相同的方式在服务器上实现它。 我们需要:

  • 检查子弹是否足够靠近服务器上的任何播放器。
  • 每当某个玩家被击中时,向所有客户发送事件。
  • 让客户听见撞击事件并使撞击时飞船闪烁。

您可以尝试完全自己完成此操作。 要使播放器在被击中时闪烁,只需将其alpha设置为0:

player.sprite.alpha = 0;

并且它将再次轻松回到完整的Alpha(这是在播放器更新中完成的)。 对于其他玩家,您会做类似的事情,但是您必须注意在更新功能中使用类似以下内容将他们的Alpha重新设置为一个:

for(var id in other_players){
 if(other_players[id].alpha < 1){
        other_players[id].alpha += (1 - other_players[id].alpha) * 0.16;
    } else {
        other_players[id].alpha = 1;
    }
}

您可能需要处理的唯一棘手的部分是确保玩家自己的子弹不会击中它们(否则,每次发射时,您总是会被自己的子弹击中)。

请注意,在此方案中,即使客户端尝试作弊并拒绝确认服务器发送给他们的匹配消息,也只会改变他们在自己的屏幕上看到的内容。 其他所有玩家仍然会看到该玩家被击中。

6.顺畅的运动

如果您按照所有步骤进行了到此为止,我想向您表示祝贺。 您刚刚制作了一款可以正常工作的多人游戏! 继续,给它发送一个朋友,观看在线多人联合玩家的魔力!

游戏功能齐全,但我们的工作并没有止步于此。 我们需要解决一些可能影响玩家体验的问题:

  • 除非每个人都没有快速的联系,否则其他玩家的动作看起来会非常不稳定。
  • 子弹可能不会反应,因为子弹不会立即发射。 它等待从服务器返回的消息,然后才出现在客户端的屏幕上。

我们可以通过在客户端上插入船舶的位置数据来修复第一个。 因此,即使我们没有足够快地收到更新,我们也可以将船平稳地移动到应该到达的位置,而不是将其传送到那里。

子弹需要更多的技巧。 我们希望服务器管理子弹,因为那样可以防止作弊,但是我们也希望得到发射子弹并看到子弹的即时反馈。 最好的方法是混合方法。 服务器和客户端都可以模拟项目符号,而服务器仍会发送项目符号位置更新。 如果它们不同步,则假定服务器是正确的,并覆盖客户端的项目符号位置。

我上面描述的实现项目符号系统不在本教程的讨论范围之内,但是很高兴知道这种方法的存在。

对船只的位置进行简单的插值非常容易。 与其直接在第一次接收新位置数据的更新事件上直接设置位置,不如保存目标位置:

// Update positions of other players 
if(id != socket.id){
  other_players[id].target_x  = players_data[id].x; // Update target, not actual position, so we can interpolate
  other_players[id].target_y  = players_data[id].y;
  other_players[id].target_rotation  = players_data[id].angle;
}

然后,在更新功能(仍在客户端中)中,我们遍历所有其他播放器,并将其推向该目标:

// Interpolate all players to where they should be 
for(var id in other_players){
    var p = other_players[id];
    if(p.target_x != undefined){
        p.x += (p.target_x - p.x) * 0.16;
        p.y += (p.target_y - p.y) * 0.16;
        // Interpolate angle while avoiding the positive/negative issue 
        var angle = p.target_rotation;
        var dir = (angle - p.rotation) / (Math.PI * 2);
        dir -= Math.round(dir);
        dir = dir * Math.PI * 2;
        p.rotation += dir * 0.16;
    }
}

这样,您的服务器可以每秒向您发送30次更新,但仍可以60 fps的速度玩游戏,看起来会很流畅!

结论

! 我们只是介绍了很多东西。 回顾一下,我们研究了如何在客户端和服务器之间发送消息,以及如何通过让服务器将其中继给所有玩家来同步游戏状态。 这是创建在线多人游戏体验的最简单方法。

我们还看到了如何通过模拟服务器上的重要部分并将结果告知客户来确保您的游戏免受欺诈。 您对客户的信任程度越低,游戏就越安全。

最后,我们了解了如何通过对客户端进行插值来克服滞后。 滞后补偿是一个广泛的话题,并且具有至关重要的意义(有些游戏在滞后足够高的情况下就变得无法玩了)。 等待服务器的下一个更新时进行插值只是缓解它的一种方法。 另一种方法是尝试预先预测接下来的几帧,并在从服务器接收到实际数据后进行更正,但是当然这很棘手。

缓解滞后影响的一种完全不同的方法是围绕它进行设计。 使船舶缓慢转动以移动的好处不仅是独特的移动机制,而且是防止移动突然变化的一种方式。 因此,即使连接速度较慢,也不会破坏体验。 像这样设计游戏的核心元素时要考虑到滞后会带来巨大的变化。 有时最好的解决方案根本不是技术性的。

您可能会发现Glitch的一项最终功能可能是有用的,您可以通过进入左上方的高级设置来下载或导出项目:

如果您做的很酷,请在下面的评论中分享! 或者,如果您对任何问题有任何疑问或澄清,我将非常乐意为您提供帮助。

翻译自: https://code.tutsplus.com/tutorials/create-a-multiplayer-pirate-shooter-game-in-your-browser--cms-23311

  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 扫一扫,分享海报

参与评论 您还未登录,请先 登录 后发表或查看评论
©️2022 CSDN 皮肤主题:编程工作室 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值