天天育儿网,内容丰富有趣,生活中的好帮手!
天天育儿网 > 多人连线的枪战游戏

多人连线的枪战游戏

时间:2020-06-06 10:40:57

相关推荐

多人连线的枪战游戏

Simple Blueprint Multiplayer是一个完全由蓝图UMG 界面编写的游戏,可以作为如何使用蓝图的Session Nodes打造游戏中的多人部分的使用示例。 这里有一个主菜单,一个服务器列表,以及一个简单的地图,地图中有一个带有记分牌的 HUD 显示。在主菜单中点击PLAY便能作为主机创建一个 Session,并加载进入游戏地图。 其他玩家在自己的菜单界面中,点击Find games来查看所有存在的游戏主机列表,点击列表中的一个已查到的游戏时则会尝试加入它。如果出现任何错误,则会回到主菜单并显示一个错误框。

这个游戏同时也是一个说明蓝图中如何使用GameInstance来管理游戏状态的范例。GameInstance 是一个管理游戏状态的方便的手段,它能够在地图加载卸载的过程中一直存在,同时它还是用于接收错误事件的对象。 大部分 Session 相关的调用以及菜单的切换都由 GameInstance 来处理。

目前,这篇文档涵盖了在线会话节点和它对于用于多人游戏中的创建主机,发现、加入、离开游戏的实现。这篇文档将会在之后更新,来进一步说明射击游戏的其他方面,比如攻击命中其他玩家,死亡和重生,以及得分的计算。

开始/进行游戏

这个章节汉高了如何开始并进行游戏,提供了游戏的各个组件的单独分析。

如果您对虚幻引擎 4 中如何测试多人游戏还不熟悉的话,应该先看一下测试多人游戏文档。

加载游戏:

Game/Maps目录打开MainMenu地图。

MainMenu 地图打开后,点击Play按钮右边的向下箭头,将Number of players设置为 2。

Run Dedicated Server选项在这个范例中可能会造成服务器列表显示不正确,这部分还需要被修正。

点击Play按钮启动游戏。

当游戏启动后,会显示一下窗口。

上图是我们在New Editor Windows的设置下,并把每个窗口设置为640x480的分辨率,这些可以在Advanced Settings...选项内设置。

如果是在网络上测试这个范例,不像上述过程直接使用本地的多人游戏的话,应采用Standalone Play Mode的方式来加入其他人的游戏,或者自己作为主机创建并让其他人来加入。通过 PIE(Play In Editor,在编辑器中启动游戏)来进行一个实际网络的游戏目前并不稳定,这个问题我们还在处理。

对于主菜单的拆分说明如下。

一旦选择作为主机开始游戏,或者加入其他主机的游戏,则会看到下图所示的游戏窗口。

在屏幕的左上角(黄色高亮框),可以看到一些文字,这是当前角色的名字。在名字右边的框框中,是当前该角色的得分。当有玩家加入时,角色名字和得分的部分都会得到更新,来显示当前游戏中的所有玩家和对应的的得分。

上图用到的玩家名字,是来自于 LAN 的连线。但当使用诸如Steam的服务时,应该显示玩家在 Steam 的用户名。

一旦有主机已经创建后,在第二个玩家的窗口中点击Find Games按钮来显示Server List

经过一小段时间搜索后,当前可用的游戏就会显示在列表中。在这个窗口中,从左到右显示的是,服务器的名字,玩家的数量,以及该游戏主机的 ping 值。 在屏幕的左下角可以点击Refresh按钮来刷新列表,或者点击Back按钮返回主菜单。直接点击列表中的条目就会尝试连接服务器,并且在游戏中生成角色。

连入游戏后,必须要点击鼠标左键作为准备好开始游戏的信号。

当点击完成准备后(主机端或客户端),左上角会显示一个文字来显示玩家已经准备好了。

完成准备后,可以用以下这几个控制方式来玩游戏。

这个游戏的目标是射击其他玩家,命中就能得到 1 分,被命中的人则要重生。每个玩家有六次射击机会,然后就要用鼠标中间(上下滚轮)来换弹(一次一颗)。

项目设置/配置

这个章节涵盖了内容浏览器中创建(或修改)的每个素材,并有各自的描述。有几个蓝图和 UMG UI 的素材互相调用(或有依赖关系),因此如果您想要重新做这个项目中类似的事情,最好是从头开始创建并再将它们关联起来。

内容浏览器中,每种资源素材都能在它们对应的目录中找到。

游戏/蓝图/控件

游戏/蓝图

游戏/Gunslinger

游戏/Character

游戏/Fonts

游戏/Maps

配置设置

为了成功的创建主机或者连接到多人游戏中,在DefaultEngine.ini中有些设置需要做,该文件在UnrealProjectDirectory/ProjectName/Config:

打开后,需要先找到OnlineSubsystem以及要用的DefaultPlatformService

比如,要在 LAN 上连线,要添加DefaultPlatformService=Null

[OnlineSubsystem]DefaultPlatformService=Null

或者如果要在Steam上玩,需要使用这样的OnlineSubsystemSteam

[/Script/Engine.GameEngine]+NetDriverDefinitions=(DefName="GameNetDriver",DriverClassName="OnlineSubsystemSteam.SteamNetDriver",DriverClassNameFallback="OnlineSubsystemUtils.IpNetDriver")[OnlineSubsystem]DefaultPlatformService=Steam[OnlineSubsystemSteam]bEnabled=trueSteamDevAppId=480bVACEnabled=0[/Script/OnlineSubsystemSteam.SteamNetDriver]NetConnectionClassName="OnlineSubsystemSteam.SteamNetConnection"

如果要用 Steam 的话,需要额外的 SDK 和 INI 配置,跳转到该链接查看更多信息。

蓝图说明

在接下去的章节中,我们来看一下游戏的各个状态,以及驱动这些状态工作的蓝图。

我们先看看Startup的序列。

开始

MainMenu的地图中,打开Level Blueprint

关卡蓝图打开后,可以看到一部分脚本标记了"Game Logic Starts Here"。

这个备注不错,游戏正是从这部分脚本开始。游戏开始时,获取了 Game Instance,并转换MyGameInstance。这么做便能够访问 MyGameInstance 的变量、函数和脚本了,这里接下去调用了一个叫做Begin Play Show Main Menu的函数(见下图)。

Begin Play Show Main Menu被调用时,首先运行了一个预先创建的宏,叫做IsCurrentState

IsCurrentState 宏

IsCurrentState会检查In State(在宏的节点上定义) 是否和一个叫做Current State的变量一致。Current State变量是一个由State创建的枚举类型,记录游戏可能处于的每一个状态。

既然Current StateIn Satet一致,便能调用ShowMainMenuEvent这个自定义事件了。

ShowMainMenuEvent做的第一件事情是运行IsCurrentState Macro检查当前状态是否是Playing,如果True的话,则加载MainMenu的关卡,如果游戏当前在Startup的状态,则返回False。这里调用了另一个叫做TransitionToState预创建的宏。

TransitionToState 宏

下图中,在 TransitionToState 中,首先运行了IsCurrentState 宏(1)对照Current StateDesired State,如果两者一致则在屏幕上显示错误并提示原因(2)如果它们不一致,则执行Switch on State节点(3)这里会获取Current State并调用一个叫做Hide Widget的函数(这里将会隐藏当前显示的 UI 控件),或者运行一个叫做Destroy Session Caller的自定义事件(将会销毁玩家要调用的游戏 Session)。

当 Switch on State 完成后,Current State 会根据 Desired State 被更新。

完成TransitionToState宏后,我们可以继续ShowMainMenuEvent自定义事件。

上图中,IsValid节点(1)执行,它会检查Main Menu变量是否有效,第一次运行该变量会无效(如果有效的话则跳过图中的(2)和(3))。步骤(2)调用了Create Widget节点来创建一个叫做Main Menu的 UMG 控件蓝图,并将结果作为一个变量(3)保存,这样便能在以后的脚本中直接访问而无需重新创建。在(4)中Set Input Mode UIOnly节点用来限制输入仅被 UI 接收,在(5)中则将Main Menu添加到窗口并显示出来。

然后我们来看一下玩家点击 Play 来创建主机会发生什么。

创建一个游戏

在 Main Menu 加载后,可以在MainMenu控件中点击Play按钮,然后会运行下图中的脚本。

MainMenu的控件蓝图中的Designer分页上,可以为 "Play" 按钮创建一个按钮事件关联到OnClicked事件上。 当这个事件触发时,获取 game instance 并转换MyGameInstance蓝图,这样就能调用它内部的自定义事件Host Game Event了。

HostGameEvent首先运行了一个MyGameInstance蓝图内的自定义事件:ShowLoadingScreen

ShowLoadingScreen被调用时,先运行了TransitionToState 宏(并将Desired State设置为Loading Screen)。

然后继续ShowLoadingScreen事件,调用了IsValid的检查,如下图(1)部分。

IsValid检查变量Loading Widget是否有效,第一次运行是无效的(如果有效则跳过步骤(2)和(3))。步骤(2)创建了Loading Screen的 UMG 控件蓝图,并将结果保存到变量中(3),一边将来直接访问而无需重新创建。在步骤(4)中,Loading Widget被添加到窗口中,并调用Set Input Mode UIOnly在加载过程中将输入限制到 UI 上(这是会在游戏中显示)。

当 Loading Screen 显示时,脚本就回到了HostGameEvent上继续执行Create Session的节点。

Create Session节点上,Public Connections的数量(允许玩家加入该游戏 Session)设置为4。还有一个布尔变量叫做Enable LAN直接连到了Use LAN的输入上。Enable LAN变量是在主菜单上的Play Mode来切换的,我们在这个文档稍后再来讨论。如果 Session 被成功创建的话,则调用Open Level节点来加载地图并准备用来开始这个 Session 的游戏。 如果失败的话,则执行OnFailure的部分,这里会运行一个预先创建的宏,叫做DisplayErrorDialog

DisplayErrorDialog 宏

调用 DisplayErrorDialog 宏时,先会执行 (1)TransitionToState 宏切换到Error Dialog状态中。 当切换到新状态后,执行自定义事件Destroy Session Caller(2) 来销毁当前玩家的 Session(可以在 Event Graph 中找到)。 在 Session 销毁后,调用引擎的宏IsValid来检查 Error Dialog 这个变量(这是一个 Error Dialog 的控件蓝图)是否有效。

在上面的图第一次运行时,Error Dialog是无效的,如果有效的话,下图的(1)和(2)会跳过直接进入(3)。

第一次运行是,(1)将使用Create Widget创建一个 ErrorDialog 控件蓝图。(2)中将该控件蓝图记录为 Error Dialog 的变量(以便该宏下次被调用时直接使用)。 在(3)中,执行函数Set Message将输入的错误信息(下图黄色高亮框)设置到 ErrorDialog 上,并调用Add to Viewport将 Error Dialog 控件蓝图显示在屏幕上(5), 以及使用Set Input Mode UIOnly来限定输入仅被 UI 获取。

如果没有错误的话,在Open Level节点中定义的地图则会加载,玩家进入游戏

加入一个游戏

在主惨淡中,点击Find Games按钮会执行MainMenu控件蓝图中的相应脚本,见下图:

MainMenu的控件蓝图中的Designer分页上,可以为 Find games" 按钮创建一个按钮事件关联到OnClicked事件上。 当这个事件触发时,获取 game instance 并转换MyGameInstance蓝图,这样就能调用它内部的自定义事件Show Server List Event了。

ShowServerListEvent首先运行TransitionToState 宏(1)将游戏状态设置为Server List。 然后用IsValid(2)检查Server List控件蓝图变量,如果有效的话就用Add to Viewport(5)节点添加到屏幕上显示。 第一次运行时,Server List 需要先被创建一次,通过Create Widget(3)节点,并将结果保存到变量(4)中,然后再显示到屏幕上。 当显示到屏幕后,再用Set Input Mode UIOnly节点限制输入设置给 UI。

在 Server List 菜单显示的时候,在上述的步骤(5)时,步骤(6)前,ServerList 控件蓝图内还有一段脚本会被执行,这里会更新当前可用的游戏到 Server List 上。可以在 Server List 控件蓝图中看到,如下图。Event Construct节点会在该控件蓝图被构造(创建)时调用,并立刻执行了一段预先设置的RefreshListMacro宏。

RefreshListMacro 宏

RefreshListMacro 宏干了不少事情。下面先解释第一部分。

如上,当宏被调用时,首先它先设置了一个名为Refresh Button Enabled的布尔变量为False(1),这会禁用Refresh按钮,防止玩家点击。 然后一个叫做 Status Text 的变量设置为Searching...(2)并将它的Visibility设置为Visible(3),就能更新控件并显示这些文字。

在(4)的位置,有一个叫做Found Session Widgets的数组变量,它是一组 ServerRow 的空间用来刷新 Server List。对每个当前存在的元素调用Remove Child(5)节点。 这将会先删除数组中所有的内容。在(6)的位置,Found Session Widgets变量再次被清理以消除先前保存过的 Session,这样在继续进一步查找前先把所有的东西清理干净。

然后,该宏获取 game instance 并调用Cast To MyGameIntance节点(下图 1 处)来访问Enable LAN的值(在查询 session 时会用到)

Find Session节点(2)用来查询当前存在的 session(会作为返回值输出)。在这个节点中,还可以设置Max Results(返回查询结果的数量)。Find Sessions的返回结果保存在Found Sessions数组变量中(3)。这里会有一个Branch(4)来检查结果,如果是 0 的话, 继续True的输出(如果不是的话则作为False输出)。如果Find Sessions的节点执行时出了什么错误,则会触发OnFailure输出,并修改Status Text显示为Search failed(5)通知用户查找 session 并未完成。

Branch后(上图中的 4),如果是True(也就是并未找到任何 session),Status Text会被设置为No sessions found(下图中的 7), 并且Refresh Button Enabled变量被设置为True(8),这样玩家便能点击Refresh按钮以再次查找游戏。

如果有 Session 找到,Status Text Visibility被设置为Collapsed(1)便能将该控件隐藏掉。然后用了ForEachLoop节点(2)来处理找到的每个result, 并使用Create ServerRow节点(3)为每个结果创建一个 Server Row 控件。这就是 Server List 里每个独立的行项。

在(4)的位置,Results也被添加到Found Session Widgets数组里,然后再调用 ServerRow 控件蓝图(用于生成服务器名称,玩家数量和 Ping 值)中的 Set Serach Result 函数。 在设置完 ServerRow 控件蓝图的内容后,Add Child节点(6) 用来给每个 ServerRow 控件设置内容,并设置Scrolling Servers显示在 Server List 菜单中。

将每个找到的 Session 都有了 ServerRow 控件创建后,把它们添加到Scrolling Servers控件上,ForEachLoop完成,并将Refresh Button Enabled变量设置为True(8)。

在运行了RefreshListMacro搜索 Session 后,任何找到的可用 Session 将会显示在Server List的菜单中。

点击Refresh按钮将会再次运行RefreshListMacro来搜寻 Session。点击Back按钮将会执行自定义事件ShowMainMenuEvent(这在开始章节有说明)。

在服务器列表中点击其中一个服务器将会触发在ServerRow控件蓝图中的OnClicked事件(见下图)。

ServerRow控件的OnClicked事件,先获取了 game instance,并使用Cast To MyGameInstance节点来进一步调用它的Join from Server List Event事件。ServerResult变量(由RefreshListMacro的过程中生成)被传入MyGameInstance的蓝图,并让玩家点击时加入服务器。

MyGameInstance的蓝图中的JoinFromServerListEvent(下图)被调用时,先运行自定义事件ShowLoadingScreen,在创建一个游戏章节作了描述。

当 LoadingScreen 显示的时候,Join Session 节点会尝试加入玩家当前点击的由 ServerRow 控件提供的 SearchResult。 如果成功的话,玩家就能加入该服务器,并在游戏中生成角色。如果有错误的话,就会执行DisplayErrorDialog并显示Failed To Join Session错误信息

游戏性

在玩家控制角色前,角色和游戏状态都需要被设置好(或者,比如要加入一个游戏时,游戏自身需要得到更新,并被告知有新玩家要加入进来)。 当点击主菜单的Play按钮,或者从 Server List 中选择一个服务器加入时,首先要调用GameMode蓝图中的Post Login事件,这里将会启动下图所示的设置过程。

Event Post Login触发后,先运行了一个引擎的宏,叫做Switch Has Authority(1),这里会检查脚本是否在一个具有网络权威性(Network Authority)(在服务器上)的地方运行还是在一台远程机器(一个客户端)上运行。 这个过程会在服务端为主机玩家和加入游戏的客户端运行,Remote的输出并不作任何事情,而Authority的输出会继续执行 Post Login 的脚本。

还有一个关于Switch Has Authority的示例,请查看在蓝图中检测网络主机和远程客户端页面。

在(2)处,新玩家的PlayerState转换为 CastMyPlayerState蓝图,这里会设置Player Number(3)用来保证玩家记分牌中的顺序和玩家顺序一致(每个新玩家的加入,都会添加到记分牌中)。 在设置 Player Number 后,New Player 被转换 CastMyPlayerController蓝图,用于调用ClientPostLogin这个自定义事件。

如图,ClientPostLogin事件的明细面板中还有一些属性设置。

Graph区块下,Replicates选项设置为Run in owning Client并且勾选了Reliable框。通过这两个选项,我们是在服务端运行的这段脚本,但该节点则仅在当前所属权的客户端上执行。Reliable的设置保证了这个脚本一定会被客户端执行到,而不会因为严重的网络负载而丢弃(大部分处理一些视觉效果同步复制的远程调用都是Unreliable来避免过多的消耗网络负载,在这个例子下,我们需要该事件必须执行,因此使用Reliable)。

确认了脚本应该在何处触发后,Setup Ingame UI函数就能进一步调用了。

这个函数以Branch(1)开始,检查当前的目标是否是本地玩家的 PlayerController,如果是的话,则使用Create Widget(2)节点创建 HUD 控件蓝图。 然后将 HUD 控件保存到一个叫做HUD Widget的变量(3)中,并Add to Viewport(4)来把它添加到玩家窗口中。在(5)处,再用一个Create Widget节点创建InGame Menu控件蓝图, 并保存到InGameMenuWidget变量(6)中,这会在玩家点击按钮调用游戏内菜单时显示。

到这里,"login" 的过程就完成了。角色的设置在Character Animation Blueprint中,定义了角色的每个动作,这部分内容这里并不涵盖,如果想了解的话需要去浏览Animation Blueprints, 文档在动画蓝图这里。

Character Animation Blueprint设置后,关卡Level_01就打开了,在这个地图的关卡蓝图中,

上图中的Event Begin Play是这个地图的事件,调用了Cast To MyGameInstance节点,并调用Start Playing State函数。这是一个MyGameInstance蓝图内的函数,用于将 game state 设置为Playing,如下图所示。

在 state 被设置为Player后,通过调用Set Focus To Game Viewport的节点(上图中的(3)),鼠标会被锁定在游戏窗口内,并调用Set Input Mode Game Only节点(上图的(4))使 UI 不再截获用户输入。

从游戏中离开

从一个游戏中断开的过程非常简单直接,只需要关闭一些 UMG 控件的显示,并执行第一次加载游戏进入主菜单同样的过程即可。

在游戏中按Q键后,则能看到游戏内的暂停菜单,点击Leave Game选项,则执行在InGameMenu控件蓝图中的以下步骤:

在上图中,点击了 Leave 按钮后触发了这个事件(1)接着 通过获取到当前玩家并转换 CastMyPlayerController的蓝图(2)。在MyPlayerController蓝图内的函数Hide in Game Menu(3)如图这样被调用。

这部分脚本拿到In Game Menu Widget变量(先前创建并保存的变量),并使用Remove From Parent节点来将它从屏幕上移除。 然后,调用Set Input Mode Game Only来防止玩家操作其他 UI,一直到我们再打开 UI 的输入为止。

然后脚本返回到InGameMenu的控件蓝图上,并使用Cast To MyGameInstance节点(4)来得到当前 game instance 并调用它内部的函数Show Main Menu Event, 接下去先执行IsCurrentState 宏,它之后的蓝图内容就是开始章节中加载游戏的部分。

如果觉得《多人连线的枪战游戏》对你有帮助,请点赞、收藏,并留下你的观点哦!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。