与戈达和Nakama制作在线多人游戏

2021-03-11 06:12:29

这是由David Snopek编写的客人博客文章,他们在鱼类游戏教程上工作。

“鱼游戏”为戈达是A2-4球员在线游戏,内置了Godot Guareengine,作为Nakama,Anopen-Source可扩展游戏服务器的演示。

正如你所看到的,“鱼游戏”是一个狂热的战斗royale主演弗里斯鱼 - 最后一条鱼胜利!游戏设计受到鸭游戏的影响(除了鸭子之外,而不是鸭子;-))。

如果您想自行尝试,您可以从发布页面下载可播放的构建ForWindows,Linux或MacOS(请参阅此处的控件)。

在本教程中,我们将通过Nakama浏览代码的每个代码的每个部分,以涵盖您需要知道的所有原则和API,以创建您自己的在线多人游戏,以便在哥登和Nakama中创建您自己的在线多人游戏。

本教程假设您已经熟练在Godot游戏引擎中的游戏开发,但之前没有使用Nakama。

您可以从Github下载完整的源代码。我建议使用0.1.0版本,因为这是我在写这个教程时看的版本。游戏的Newerversions可能存在于阅读此问题的时间中,并且Thisturor中的代码可能不匹配。

在本地设置Nakama服务器的最简单方法是通过Docker,Andin事实,有一个Docker-Compose.yml,包括在“Fishgame”的源代码中。

因此,如果您在系统上进行了Docker ComposeInstall安装,您需要做的就是导航到您放置“鱼游戏”源代码的目录以及运行此命令:

但是,如果您不想使用Docker,它也可以直接在系统上安装anakama。

(可选)编辑autoload / Online.gdfile并替换顶部的变量,并为您的nakama server替换为右值。如果您在本地运行默认设置的Nakama服务器,那么您不需要更改任何内容。

按F5或单击右上角的播放按钮开始游戏。

如果您没有使用“鱼游戏”包含的Docker-Compose.yml,那么在您首先在服务器上创建它之前,“排行榜”将无法使用。

为此,将nakama / data / modules / fish_game.lua文件复制到Nakama服务器保存其数据的模块/目录中,然后重新启动Nakama服务器。

虽然“鱼游戏”是一个非常简单的示例游戏,但它是真实的典范。它是一个真实游戏的结构化,所以它的代码并不像在更有人工的例子中那样简单。

在某些地方,在教程中通常会看到您通常会看到的间接更多。拥有多个课程,称其他类来做某事,而不是直接执行它。

代码充满错误检查,处理角落案例,寻址竞争条件以及使代码看起来有点丑陋和更复杂的其他东西。这些是大多数教程中经常省略的东西,但在真实世界游戏中绝对必要。

此外,在本教程中,我们将从游戏中查看各个代码片段,而不是每个文件中的完整源代码。这是为了使我们在本教程中展示我们在教学的具体事物。

在几乎所有情况下,在同一文件(甚至方法)中存在更多代码,如果您希望完整地查看它,请参阅Github的源代码。而且,因为本教程的重点是Nakama,我们不会看几乎所有实现游戏的代码。

在我们开始挖掘网络代码之前,让我们首先谈谈游戏的高级结构。

当您第一次在Godot编辑中打开项目时,您将被呈现游戏的“主要场景”。此场景负责将游戏的所有部分捆绑在一起。

请注意Uilayer /屏幕节点下的所有节点,如ConnectionScreen,MatchScreen等。这些是所有菜单或“屏幕”(因为它们在代码中称为),这对于与Nakama进行交互很重要(即登录,开始匹配等)。通过out的代码,您将看到如下行:

这是指您在此处看到的匹配节点(这是主/屏幕/匹配屏幕的实例)。

ui_layer.show_message(" ..."):在屏幕顶部显示一条消息。只有一条消息一次只能示出。

主要场景还处理游戏生命周期:初始化游戏(在本地或在线模式),启动/停止匹配,并保持得分。

但是,它是游戏场景,它对游戏的所有“游戏零件”负责。

但是大部分非常有趣的功能,从游戏玩法和网络角度来看,都在玩家场景中。

它由夫妻精灵,碰撞形状,区域,动画播放器和玩家可以制作的所有声音组成。

但是,最重要的部分是其国家机器。播放器能够进入(例如空闲,移动,跳转等)的每个状态都由StateMachine节点下面的节点表示。播放器控件,动作和动画都与当前状态进行了变化。

我们不会在本教程中深入讨论状态机模式,或者真的很多关于如何实现游戏模式 - 这是一个关于Nakama之后的教程!但是要了解国家机器很重要,因为我们需要在网络上同步玩家的状态,以便在线播放。

var nakama_server_key:string =' defaultKey' var nakama_host:string =' localhost' var nakama_port:int = 7350var nakama_scheme:string =' http'

这些默认值将允许您通过包含的Docker-Compose.yml连接到Nakama服务器设置,但是,如果您使用的是其他Nakama服务器,请将其替换为您的情况的正确值。

在查看自动加载/目录时,您可能已经注意到了AutoLoad / Build.gd中的另一个单例。在Git存储库中,此文件仅包含注释:

它替换为在构建游戏发布时连接到生产Nakama服务器的值。这是通过Github操作自动完成的,使用脚本/生成--Build-variables.sh来完成。实际值存储在Github加密的秘密中,因此他们不需要在Git中提交。

如果要为游戏设置类似的构建系统,请查看Github操作工作流配置.github / workflows / godot-export.yml,或者如果使用gitlab,则在.gitlab中有一个gitlab ci配置 - ci.yml也是如此。

为了与我们的Nakama服务器进行互动,我们需要获取Nakamaclient对象。 Nakamlient类来自Nakama的Godot Client,它包含在Addons / com.hericlabs.nakama下这个项目的源代码中。

var nakama_client:nakamaclient setget _set_readonly_variable,get_nakama_clientfunc _set_readonly_variable(_value) - > void:passfunc get_nakama_client() - > nakamaclient:如果nakama_client == null:nakama_client = nakama.create_client(nakama_server_kear,nakama_host,nakama_port,nakama_scheme,nakama.default_timeout,nakamalogger.log_level.error)返回nakama_client

这允许我们引用Online.nakama_client并获取为Nakama服务器配置的Nakamaclient对象。

var nakama_session:nakamasession setget set_nakama_sessionsignal session_changed(nakama_session)func set_nakama_session(_nakama_session:nakamasession) - > void:nakama_session = _nakama_session emit_signal(" session_changed" nakama_sessions)

与Online.Nakama_Client不同,它不会为我们构建一个对象,我们希望在项目中的其他地方创建一个Nakamasession并将其分配给Online.nakama_session。

我们这样做,因为有多种方法可以使用Nakama(电子邮件/密码,Steam,Facebook等)进行身份验证,所有方法都需要不同的工作流程和UI。因此,我们让UI以任何方式进行用户身份验证,并在任何方式中,并在Online.nakama_session中存储会话。然后项目的其他部分可以在线使用.nakama_session,并连接到(或exiting()开)“session_changed”信号,以知道会话创建或更改时。

我们将在即将到来的部分“用户身份验证”部分中创建Nakamasession并将其分配给Online.nakama_session。

使用Nakamaclient,我们可以对Nakama进行简单的要求并获得回复。下面它实际上是制作正常的HTTP请求。

但是,我们也希望使用Nakama的实时多人游戏API,因此我们需要打开一个NakamaSocket,它给Nakama提供持久的双向连接(通过WebSockets),允许Nakama随时向我们发送给我们的消息。

var nakama_socket:nakamasocket setget _set_readonly_variable#内部变量用于初始化socket.var _nakama_socket_connecting:= falseSignal socket_connected(nakama_socket)func connect_nakama_socket() - > void:如果nakama_socket!= null:返回if_nakama_socket_connecting = true var new_socket = nakama.create_socket_from(neakama_client)fiudt(new_socket.connect_async(nakama_session),"已完成")nakama_socket = new_socket _nakama_socket_connecting = false emit_signal (" socket_connected",nakama_socket)func is_nakama_socket_connected() - > BOOL:返回nakama_socket!= null&& nakama_socket.is_connected_to_host()

#如果未连接,请将套接字连接到实时Nakama API。如果没有联机.is_nakama_socket_connected():Online.Connect_nakama_socket()产量(在线," socket_connected")

在线单例中的任何内容都是特定的“鱼游戏”,它没有超越Nakama addon的依赖关系。您可以轻松将自动加载/ Online.gd复制到您自己的项目中!

事实上,所有单例都设计用于在其他项目中可重复使用,并提供清晰的外部API。

在用户可以加入匹配之前,他们需要首先使用Nakama服务器进行身份验证,这是创建Nakamasession的方式。

Nakama提供了许多不同的身份验证机制,包括电子邮件/密码,设备ID和各种社交服务(Facebook,Google,Steam等)。为简单起见,我们将使用电子邮件和密码身份验证。

这是一个非常简单的标签UI,具有“登录”和“创建帐户”选项卡,具有必要的字段,如“电子邮件”,“密码”等。

单击“创建帐户”按钮将触发Main / Screens / ConnectionScreen.gd中的_on_createaccountbutton_pressed()方法。让我们看一下......

这些存储在成员变量中,当我们的会话已过期时将自动重复使用。我们稍后会详细讨论这一点。

然后,我们抓取此选项卡上的其他UI字段的值:

在输入上进行一些简单的验证,隐藏屏幕并显示友好的消息:

如果电子邮件=='&#39 ;: UI_Layer.show_message("必须提供电子邮件")如果password =='&#39 ;: ui_layer.show_message("如果用户名=='&#39 ;: UI_Layer.show_Message("必须提供用户名")返回Visible = false u_layer.show_message("创建帐户...")

因此,此时,用户只需在背景(这是游戏地图)的顶部看到消息。

接下来,我们需要使用Online.nakama_client来尝试使用Nakama进行身份验证,并创建一个Nakamasession:

请注意,我们将其传递为第4参数:这告诉Nakama如果它不存在,我们希望创建帐户。

如果nakama_session.is_exception():Visible = true var msg = nakama_session.get_exception()。消息#nakama将注册视为登录,所以这是我们得到的,如果#电子邮件已经使用,则是我们得到的。如果msg =='无效凭据。&#39 ;: msg ='电子邮件已经使用。' elif msg =='&#39 ;: msg ="无法创建帐户" ui_layer.show_message(msg)#我们始终设置在线.Nakama_Session,以防有些东西在" session_changed"信号。在线.nakama_session = null.

我们分配Online.nakama_session = null,以便可以让等待有效会话的其他代码意识到失败。当我们在会话过期后重新连接时,这将变得重要。

_save_credentials()方法基本上只是用用户的凭据写入JSON文件,因此可以稍后加载它们。这并不是非常安全 - 如果这是一个直播游戏,我建议不包括此功能,或以某种方式加密文件。但是,这是一个非常好的功能在开发期间拥有,因此您不必一遍又一遍地键入密码。 :-)

然后我们将Online.nakama_session设置为新创建的会话并隐藏任何消息,然后显示匹配项,允许用户加入匹配项。

func _on_loginbutton_pressed() - > void void:visible = false如果_reconnect:ui_layer.show_message("会话过期!重新连接...")else:ui_layer.show_message("登录...")var nakama_session =产量(Online.nakama_client.authenticate_email_async(电子邮件,密码,空,假),"完成")如果nakama_session.is_exception():Visible = True UI_Layer.show_message("登录失败!&# 34;)#清除存储的电子邮件和密码,但单独留下字段,以便#用户可以尝试纠正它们。电子邮件=''密码='' #我们始终设置在线.Nakama_Session,以防" session_changed"信号。 Online.nakama_session = null else:如果save_credentials:_save_credentials()在线.nakama_session = nakama_session ui_layer.hide_message()如果_next_screen:ui_layer.show_screen(_next_screen)

这是创建帐户的许多相同的东西,其中一些小差异:

根据_reconnect变量为真或假的情况,显示不同的消息。

传递给nakama_client.authentice_email_async()的第4个参数是假的,所以我们不会自动创建一个帐户,如果一个不存在。

在失败时,我们清除了电子邮件和密码成员变量,这可以防止它们在会话到期后重新连接时自动重复使用。

在成功时,我们显示名称存储在_next_screen变量中的屏幕。默认情况下,这是匹配的屏幕,但自动重新连接时可以是任何屏幕。

正如我们所提到的许多次,Nakama会话在一段时间后到期,然后您需要再次进行身份验证。

默认情况下,他们在60秒后到期,这是人为低的,以强制开发人员确保他们的游戏可以处理它。在一个直播游戏中,拇指规则是将到期时间配置为平均会话的长度的两倍。

无论如何,会议最终会过期,所以您的游戏需要一种处理它的策略!

一种方法使用户每次会话到期时都会重新输入其用户名和密码。但是,它们不太可能在匹配过程中更改密码,因此您的游戏也可能尝试使用上次使用的相同凭据重新连接。这是ConnectionScreen中使用的方法。

当显示任何UI屏幕时,将使用2nd参数到ui_layer.show_screen(" screenName",{...})作为信息参数来调用它的_show_screen(info:dictionary = {})方法。这是ConnectionScreen的:

Func _show_screen(信息:字典= {}) - > void:_reconnect = info.get('重新连接' false)_next_screen = info.get(' next_screen'' matchscreen')tab_container.current_tab = 0#我们有一个存储的电子邮件和密码,尝试立即登录。如果是电子邮件!=''和密码!='&#39 ;: do_login()

所以,如果你打电话给Ui_layer.show_screen(" connectionscreen" {creoctnect = true,next_screen ='排行榜屏幕'}),那么特殊的私有成员变量_reconnect将设置为true, _next_screen将设置为排行榜屏幕。

如果我们有一个已从用户的最后一次成功登录尝试中存储的电子邮件和密码,那么它将尝试立即自动登录。

这允许使用有效的Nakama会话的游戏的任何部分来执行以下操作:

#如果我们的会话已过期,请再次显示ConnectionScreen。如果在线.nakama_session == null或whine.nakama_session.is_expired():ui_layer.show_screen(" connectionscreen" {creoctnect = true,next_screen = null})#等待查看我们是否收到新的有效会话。产量(在线," session_changed")如果在线.nakama_session == null:返回

显示ConnectionScreen将自动尝试再次登录,但是由于我们传递{next_screen = null},如果成功,它不会显示匹配屏幕(或任何屏幕)。

然后,我们在“session_changed”信号上产生(),这将暂停执行当前方法,并且一旦发射信号就恢复。

如果在线恢复,请不要包含会议,嗯,我们现在必须放弃。这意味着身份验证失败,现在正在显示连接屏幕,以便用户可以尝试输入不同的电子邮件和密码。

或者,如果屏幕需要立即使用Nakama会话,则在其_show_screen()方法中,它可以使用next_screen设置为自身的UI_Layer.show_screen()。例如,在排行榜上我们有:

Func _show_screen(信息:字典= {}) - > void:#如果我们的会话已过期,请再次显示ConnectionScreen。如果在线.nakama_session == null或whine.nakama_session.is_expired():ui_layer.show_screen(" connectionscreen" {creoctnect = true,next_screen ="排行榜屏幕"})返回

这些是我们将在游戏中的几个地方使用的模式,以确保我们有一个有效的Nakama会话。

func _ready() - > void:$ panelcontainer / vboxcontainer / matchpanel / matchbutton.connect("按下" self," _on_match_button_pressed",[onlinematch.matchmode.matchmaker])$ panelcontainer / vboxcontainer / createpanel / createButton。连接("按下",self," _on_match_button_press",[onlinematch.matchmode.create])$ panelcontainer / vboxcontainer / joinpanel / jotbutton.connect("按下",自我," _on_match_button_pressed",[onlinematch.matchmode.join])

但请注意,不同的参数将根据按下的按钮传递给方法。

onlineMatch是另一个单身,在autoload / onlinematch.gd中定义,负责管理在线匹配,包括:创建/加入匹配,跟踪其他玩家,向他们发送消息,并离开匹配。

Func _on_match_button_pressed(mode) - > void:#如果我们的会话已过期,请再次显示ConnectionScreen。如果在线.nakama_session == null或whine.nakama_session.is_expired():ui_layer.show_screen(" connectionscreen" {creoctnect = true,next_screen = null})#等待查看我们是否收到新的有效会话。产量(在线," session_changed")如果在线.nakama_session == null:返回

这与上面讨论的模式是相同的模式,以确保Nainname_Session是一个有效的会话,如有必要,自动重新连接。

接下来,我们需要连接NakamaSocket才能访问Nakama的实时多人游戏API:

#如果未连接,请将套接字连接到实时Nakama API。 如果没有联机.is_nakama_socket_connected():Online.Connect_nakama_socket()产量(在线," socket_connected") 这是我们之前讨论过的另一种模式,以确保在线.nakama_socket连接到Nakama。 它将Nakama_Socket传递到OnlineMatch.Create_Match()进行加入匹配的实际工作。 和EAC ......