【编程】【从小白到大佬的游戏制作教程1】RPG程序基础篇

大家好,我是《彩华》和《幻想乡传说》的制作人时雨,负责了两款游戏的策划和代码的编写,从今天开始我正式入驻国服开发者论坛啦~希望今后能和大家多多分享多多交流,让我们共同进步。从本篇开始我将开启一个系列教程,尽可能避免繁琐的逻辑,用最通俗的语言最简单的程序让大家理解,如果有遗漏还请各位指出,另外如果能帮到各位,加深大家对程序的理解那就更好了。
image

两款游戏的链接:
《彩华》https://www.roblox.com/games/5668491711/
《幻想乡传说》Gensokyo Densetsu - Roblox

【本篇简介】
我将从程序基础开始介绍一款RPG游戏(我目前最擅长的游戏类型)的制作过程。讲解关于事件发送和接收,函数发送和接收相关的内容。

【正文】
RPG游戏基本结构
RPG游戏在我的理解中是相对简单的一种游戏类型,但想要做出优秀的RPG游戏却非常不容易。RPG游戏我一般将结构分为输入和输出两种。

※所谓输入就是资源的获取,资源就是数据,例如游戏中金币、钻石、物品之类的资源通过购买、刷怪或者PVP获取等。
※所谓输出就是资源的消耗,例如抽奖、升级强化等

玩法通常来自于输入部分,主要是老三样:刷怪、BOSS战、PVP。
列举一款比较有名的游戏《Anime Fighting Simulator》,资源获取是各类训练所获得的数据成长,BOSS战和PVP分别对应世界BOSS和Tournament。这款游戏的链接:https://www.roblox.com/games/4042427666/

关于理论上的介绍就此打住,我相信各位制作游戏的小伙伴一定都是热爱玩游戏的人,也一定玩过不少RPG游戏,所以在此不多赘述了。

RPG程序基础
RPG游戏制作不需要很高的程序水平,只需要明白基本的服务器和客户端逻辑即可。
相信不少制作者此前完全没有接触过C#,C++以及Java这些高级编程语言,但事实证明这些对我们学习和编写lua程序是没有任何影响的。
lua语言在许多游戏,例如饥荒中都有广泛应用, CSDN的lua程序员平均工资屡次挤进前五,可见这门语言有着与最传统的三大编程语言相当的地位。因此可以抛开以往对lua的一切成见,潜心研究这门语言,你也一定可以成为一位了不起的程序员。

事件与函数
这里所讲的事件和函数并非通常我们所说的事件和函数,而是“两大家族”我认为在RPG制作乃至于其他各类多人游戏制作中最为重要的一环。
代表bindable家族的事件和函数
image
代表remote家族的事件和函数
image

事件和函数的区别在于前者是不需要返回值(不需要整个程序等另一方发送响应直到接到响应才继续运行后面的代码)后者是必须有返回值的(只有接到响应才能继续运行,否则会一直挂起这个运行中的脚本)
其中remote家族的RemoteEvent是服务器向客户端通信、客户端向服务器通信的最常见事件。

服务器向客户端通信形式如下:
首先请在ReplicatedStorage中新建一个RemoteEvent并更名为MainEvent
image
image
在任何一个Script(服务器脚本)中定义ReplicatedStorage为ClientStorage(ReplicatedStorage本质就是客户端脚本,个人觉得这样更加易于理解)并获取刚才的事件
image
我们可以在玩家加入游戏时发送事件给该玩家,也可以在任何能获取到player的时候发送事件给玩家
image
而在客户端我们需要同样的方式获取刚才的事件,客户端脚本最好放在这里:
image
在脚本中接收事件
image

客户端向服务器通信形式如下:
由于刚才的事件已经被占用了,我们需要重新建立一个新的事件
image
在客户端获取到事件并发送事件给服务器。这里我们定义一个参数str(代表string)内容随意,我的是“123456”。和事件一起发送。
image
在Script中接收事件,以下两种写法都是正确的,str只是一个参数在客户端和服务器的名称不需要相同
image
image

bindable家族的BindableEvent是服务器向服务器端通信、客户端向客户端通信的最常见事件。
服务器向服务器通信形式如下:
首先新建一个BindableEvent,对其随意命名
image
image
在服务器脚本中获取该绑定事件,并且发送给事件


在另外一个服务器脚本中同样获取该绑定事件,并接收事件

客户端向客户端通信形式如下:
我们养成好习惯把客户端脚本都放在玩家初始化下
image
我们先重新建立一个BindableEvent
image
在上面用到的那个LocalScript(客户端脚本)中获取事件,并获取玩家。我定义了一个要发送的参数str,其值为“123456”的字符串。并将玩家的UserId与字符串一同发出


在另一个新建的客户端脚本中接收事件

这里需要注意8-10行这一步是判定发送事件的是否是玩家本人。这个操作在很多教程中是没有的,原因是大部分事件发送都是从玩家自己的客户端到客户端,脚本是在玩家初始化下的,但是我见过有些程序员喜欢把客户端脚本写在ReplicatedFirst(客户端优先加载)中,请注意,在客户端优先加载中的脚本就算是客户端向客户端通讯也会引起我的客户端发送到别的玩家客户端这样情况出现,因为客户端优先加载的所有程序实质是所有人都一模一样地复制了一遍。因此我们养成好习惯,在发送客户端到客户端BindableEvent时统一加上playerId的判定。return等于return nil就算运行到这一步返回空值,也就是结束函数。

我们讲完了Event,再来看看Event的大表哥,Function。这是比Event更高级的Event,优点是它会等待事件发送出去后得到响应再进行后面的程序,但缺点是如果事件发送出去后另外一边的程序没有响应那就会一直wait(挂起)下去,也就是我们所说的程序卡死。
那么为什么会出现一直得不到响应的情况呢?第一种原因是网络故障,也就是我们说的网不好,但这种情况下服务器会优先断开连接,所以在我们无限等下去之前就被踢出了游戏,因此这种情况我们无需担心是自己代码引起的问题。第二种原因是客户端退出,也就是在我们向客户端发送Function的时候客户端没了,那自然也就得不到任何响应,程序直接卡死。
所以为了避免出现第二种情况,我们不要向客户端发送Function,向客户端通讯一律使用Event!

接下来我们来看看Function的具体用法(这里就不讲客户端作为接收端的情况了),
客户端向服务器发送函数形式如下:
首先在ReplicatedStorage(客户端存储)中新建RemoteFunction,随意命名
image
image
在客户端脚本中获取刚刚新建的Function,并向服务器发送。注意这里的callback可加可不加,但我们养成好习惯,最好加上,


服务器接收到Function后连接到FunctionHandler(函数处理器),注意连接的函数后面不要带参,参数写到前面声明的函数括号里面去。以下两种形式都对,也就是说我们可以return任何东西,除了return nil以外。


然后再回到刚才的客户端。当然你熟练了这一步可以和前面客户端那部分一气呵成。
假定刚才我们的服务器return true,那么这一步就是

这个步骤是客户端先发送Function,服务器接收;服务器接收后,再返回给客户端,客户端得到return值后print(“OK”)。这有点类似于互联网协议中的2-way Handshake(不是3-way Handshake)。

如果是服务器发服务器,那么我们就改用Invoke和OnInvoke,但需要注意,服务器们永远是在一起的,也就是说所有的服务器间通讯事件和服务器间函数都是瞬间完成,那么我们就不需要考虑延迟问题,所以理论上来说BindableFunction基本很少用到。

【实战经验】
InvokeServer是Event的升级版。举一个例子,在幻想乡传说中,我需要在玩家点击一个按钮后召唤出位于服务器的宠物,那么在我点击按钮后需要按钮变成“Unsummon”


但是问题在于我不知道我的宠物是否成功召唤出来,万一没有召唤出来我又点击了一次按钮那事件就会发送两次,其中又涉及到各种更加累赘的判定。
这里我使用了RemoteFunction在服务器响应传回来后,也就是宠物成功被召唤出来后我的按钮变成了Unsummon。

我们试想,如果我像许多新手程序员那样用事件通讯来完成,我需要在玩家召唤的时候向服务器先发送一个事件,在服务器完成召唤的表现后再发送一个事件到客户端,客户端再修改按钮的文本。原本只需一个Function解决的问题我用了两倍的功夫。

关于性能方面的优化以及Bot和Synx之类大家更加感兴趣的内容我们放到下一期再聊~
希望这篇内容能帮到你!
image

8 个赞

啊!感谢大佬的分享,新手入坑,以后多交流呀!!!

可太棒了这个教程

关于我们    加入我们    条款    隐私政策
©2021 Roblox Corporation、Roblox、Roblox 标志及 Powering Imagination 是我们在美国及其他国家或地区的注册与未注册商标。
粤ICP备20013629号