本节将解释如何用goworld制作带有登录注册功能的小游戏,会制作一款名为“打工”的游戏。
登陆界面如下图,玩家可以注册账号、然后登录。
登录后进入如下图的游戏界面,界面显示拥有的金币数量(如253),玩家点击打工按钮会得到1个金币。金币为持久化数据,玩家下次登录金币不变。
本节教程将展示如何使用goworld开发注册登录功能,以及如何做到数据存储。
系列文章
服务端
上几节教程中,玩家连接后服务端会创建Account实体,这个Account实体代表一条网络连接。本节会新增Player实体,用它来代表玩家对象。当玩家正确输入用户名密码登录后,Account实体将把控制权转交给Player。
登录过程如下图,客户端连接后创建Account实体,登录后创建Player实体,Account把控制权转交给Player,由于Account已经完成了任务,可以摧毁。
coin.go
由于拥有多个实体,需要在主文件中注册它们。
Account.go
登录和注册功能都在Account中实现,先写个框架。Account中包含logIn的成员,它代表Account是否正在登录,这因为在登录的过程中不可以重复登录,需要一个成员用于判断。
接下来编写注册功能。goworld里封装了goworld.GetOrPutKVDB、goworld.PutKVDB等方法,他们以key-value格式向数据库读取数据。注册接口由客户端通过RPC调用,添加Register_Client方法,它接收用户名和密码两个参数,然后查询数据库,如果用户名已经存在,提示失败。否则保存一个玩家所需的三种数据。
假设玩家的账号叫cat,密码是123,关于这个玩家,数据库中会保存这么几条数据
1、用户名密码映射。 例如:password$cat = 123
2、用户名和ID映射。例如:playerID$cat = 895558625 ,ID为实体的唯一ID,goworld会保证ID不会重复
3、实体数据:ID=玩家身上的数据
为了保存初始的实体数据,可以新建一个Player实体,保存该Player对象然后销毁。Register_Client代码如下:
接下来编写登陆功能。登录也是通过客户端RPC调用,程序先判断用户名密码是否正确,然后获取玩家ID,由玩家ID去获取代表玩家的Player实体,将它加载进来。
最后调用RPC a.Call(playerID, "AccountCall", a.ID)去调用Player实体的AccountCall方法,这么做是因为Load是异步操作,无法在Load后直接获取Player实体,通过RPC调用等待Player加载完成,Player加载完成后,我们会让它回调Account的OnAccountCall方法,将控制权交给Player。
Account和Player的交互过程如下图。
代码如下:
Player.go
现在开始实现代表玩家的Player.go。对于需要保存的数据,需要在DescribeEntityType方法里面添加相应的语句,这里使用desc.DefineAttr定义了name和coin两个数据。参数中的Persistent代表该数据需要持久化,即会保存到数据库上。AllClients和Client代表他们的同步范围,AllClients代表该值一旦有变化,所有视野范围内的客户端都会收到变化信息,Client代表只有主机会收到变化信息。
在OnCreated中,给Coin设置默认值250,也就是新号拥有250个金币。SetClientSyncing(true)数据代表会同步数据给客户端。
AccountCall只是个用于回调的方法,它会调用对应Account的OnAccountCall方法。
Work_Client是客户端RPC的方法,当玩家点击“打工”按钮,客户端会发送这个RPC,服务端让金币加1。
代码如下:
Space.go
Space没有任何功能,编写一个空的Space即可。
如此,便完成了服务端的编写。
客户端
需要制作两个场景,各自包含不同的界面,一个是登录界面,一个是打工界面。
登录界面如下如,包含用户名和密码的输入框,以及连接、登录和注册三个按钮。
打工界面如下,拥有显示金钱的文本框,以及打工按钮。
login.cs
编写启动脚本login.cs,它注册实体,驱动goworld框架。
在login.cs中编写按钮的点击事件。当按下连接按钮时,触发OnConnectClick连接服务端。当按下注册按钮时,触发OnRegisterClick,它会发起RPC,调用Account实体的Register_Client。当按下登录按钮时,触发OnLoginClick,它会发起RPC,调用Account实体的Login_Client。
完成后添加该组件,并绑定按钮事件。
coin.cs
编写放置于第二个场景的脚本coin.cs,它实现了OnWorkClick方法作为打工按钮的事件,同样的发起rpc。
完成后在第二个场景添加coin组件,并绑定按钮事件。
Account.cs
编写Account实体的客户端实现,它只是个基础结构。定义了ShowInfo方法用于给服务端RPC调用打印消息。
Player.cs
接着需要编写玩家实体Player类,先编写个基础的结构。
如果该玩家的Player实体被创建,代表着登录成功,所以在OnCreated方法中跳转到打工场景。游戏的实体不会因为跳场景而被摧毁,他们只由服务端创建或摧毁。
Tick方法中调用了待实现的ReflashCoinNum,它会更新界面上的金币数量。
CreateGameObject方法展示了获取玩家属性的方法,这里通过attrs.GetStr获取player的name属性,然后给GameObject命名。
接着编写OnAttrChange_coin方法。OnAttrChange_xxx是一种固定格式,当玩家身上指定的属性发生变化该函数会被调用。这里当金币变化会打印日志。ReflashCoinNum会获取玩家身上的金币,然后更新界面。
如此,完成所有的编码工作。
测试
现在可以运行游戏,连接、注册、登录。
登录后会进入打工界面,点击打工按钮可以赚金币。
关闭客户端重新登录,或者关闭服务端重新打开,金币数量都会被保存。
推荐些资料
笔者所著《Unity3D网络游戏实战(第2版)》是一本专门介绍如何开发多人网络游戏的实战书籍,手把手教你搭建网络框架,制作大型项目。
「同步」也是网络游戏开发的核心课题。玩家的位置和旋转需要同步给其他玩家,然而网络条件差,会不同步和卡顿。笔者主讲的live《网络游戏同步算法》揭示做好同步的方法,欢迎收听。