游戏服务端开源引擎GoWorld教程——(3)手把手写一个聊天室

19 篇文章 5 订阅

看完示例,接着开始写代码。本节完成的功能是从零开始搭建一个简单的聊天室,包括服务端和Unity客户端两部分。界面如图,客户端点击链接登录,输入聊天内容,所有连接的客户端都能够在调试窗口中看到消息。

系列文章

罗培羽:游戏服务端开源引擎GoWorld教程—— (1)安装和运行

罗培羽:游戏服务端开源引擎GoWorld教程——(2)Unity示例双端联调

罗培羽:游戏服务端开源引擎GoWorld教程——(3)手把手写一个聊天室

罗培羽:游戏服务端开源引擎GoWorld教程——(4)制作多频道聊天室

罗培羽:游戏服务端开源引擎GoWorld教程——(5)登录注册和存储

罗培羽:游戏服务端开源引擎GoWorld教程——(6)移动同步和AOI

罗培羽:游戏服务端开源引擎GoWorld教程——(7)源码解析之启动流程和热更新

罗培羽:游戏服务端开源引擎GoWorld教程——(8)源码解析之gate

罗培羽:游戏服务端开源引擎GoWorld教程——(9)源码解析之dispatcher

罗培羽:游戏服务端开源引擎GoWorld教程——(10)源码解析之entity

echo服务端

这个教程分两步进行,第一步是先把服务端给搭建起来,然后编写一个回应程序,以验证最基础的消息收发,第二步是添加聊天室功能。

服务端结构

有必要再回顾下goworld的结构图,客户端连接game,经由dispatcher与逻辑服game相连。其中的gate和dispatcher都是固定的程度,我们只需要编写game的逻辑即可。

game里面有两种基本的对象,一种是Entity(实体),另一种是Space(场景)。goworld的服务端至少要定义一种Space以及名为Account的Entity。当game启动时,引擎会给每个game场景一个名为NilSpace的场景,这是个特殊场景,不能交互。当客户端连接进服务器时,引擎会随机的在某个game的NilSpace创建一个代表该连接的Account实体,由它处理玩家的逻辑。

我们会编写简单场景MySpace和Account类,用Account处理玩家的逻辑。当有玩家连接时,game的结构如下。

echo.go

现在,开始编写服务端吧。在goworld目录里新建examples/echo文件夹,然后新建echo.go的文件,编写如下代码。

package main
 
import (
    "github.com/xiaonanln/goworld"
)
 
func main() {
    // 注册自定义的Space类型(必须提供)
    goworld.RegisterSpace(&MySpace{}) 
    // 注册Account类型
    goworld.RegisterEntity("Account", &Account{})
    // 运行游戏服务器
    goworld.Run()
}

在main中注册Space和Account实体,这两个类稍后编写。最后调用goworld.Run运行游戏服务器。

MySpace.go

新建文件MySpace.go,编写如下的代码。这是个基本的结构,MySpace继承自entity.Space,它还有个空函数OnSpaceCreated,将会在空间创建时被调用。

package main
 
import (
 "github.com/xiaonanln/goworld/engine/entity"
)
 
type MySpace struct {
 entity.Space // Space type should always inherit from entity.Space
}
 
// OnSpaceCreated is called when the space is created
func (space *MySpace) OnSpaceCreated() {
 
}

Account.go

编写代表玩家的Account.go,它继承自entity.Entity。必须带有OnCreated和DescribeEntityType方法,引擎会在恰当的时候调用它们,目前留空即可。在Account中编写Echo_Client方法,客户端可以通过RPC调用“XXX_Client”方法,该方法被调用后会通过CallClient调用客户端的ShowInfo方法,并传回msg。

package main
 
import (
    "github.com/xiaonanln/goworld"
 "github.com/xiaonanln/goworld/engine/entity"
)
 
// 玩家类型
type Account struct {
    // 自定义对象类型必须继承entity.Entity
 entity.Entity
}
 
// OnCreated 在Player对象创建后被调用
func (a *Account) OnCreated() {
}
 
 
func (a *Account) Echo_Client(msg string) {
 a.CallClient("ShowInfo", msg) 
}
 
func (a *Account) DescribeEntityType(desc *entity.EntityTypeDesc) {
 
}

编写完成,即可编译并运行服务端

goworld build examples/echo

goworld start examples/echo

echo客户端

接着从零开始用Unity编写聊天室的客户端程序,新建个Unity工程,把goworld-unity-demo中Assets\Scripts\GoWorldUnity3D这整个文件夹复制过去,这里面包含了与goworld匹配的网络库。

为使网络库正常运行,还需将demo工程中的Plugin/MsgPack复制到新工程中。

复制完成后,确保没有报错就可以进入下一步。

制作界面

开始制作聊天室的简单的界面,界面如下图,拥有连接和发送两个按钮,还有一个文本输入框。

 

实现Entity

对goworld的entity/space结构中,服务端有的实体结构客户端也需要一份实现,需要实现Account。新建Account.cs,编写如下代码。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using GoWorldUnity3D;

public class Account : ClientEntity
{
 protected override void OnCreated() {
        Debug.Log ("OnCreated");
    }

 protected override void OnDestroy() {
        Debug.Log ("OnDestroy");
    }

 protected override void OnEnterSpace() {
        Debug.Log ("OnEnterSpace");
    }

 protected override void OnLeaveSpace() {
        Debug.Log ("OnLeaveSpace");
    }

 protected override void OnBecomeClientOwner() {
        Debug.Log ("OnBecomeClientOwner");
    }

 protected override void Tick() {
 //Debug.Log ("Tick");
    }

 public static new GameObject CreateGameObject(MapAttr attrs)
    {
        Debug.Log ("CreateGameObject");
        GameObject a = new GameObject ();
        a.name = "account";

        a.AddComponent<Account> ();
 return a;
    }

 public void ShowInfo(string msg) {
        Debug.Log (msg);
    }
}

Account继承自ClientEntity,其中的OnCreated、OnDestroy等方法都是固定结构,引擎会在何时的时间调用它。比如当客户端连接后,服务端会创建一个Account,它还会通过网络消息让客户端也创建一个Account对象,然后调用它的OnCreated方法。

引擎会在Account创建后调用CreateGameObject,需要自行实现,此处会在场景里创建名为account的物体,再给它添加Account组件。

ShowInfo是个自定义的方法,供给服务端RPC调用的,它会打印出Log。

echo.cs

编写完Account,编写如下的启动代码。程序启动时调用RegisterEntity注册Account,所有使用的实体都必须要注册,在Update中调用GoWorldUnity3D.GoWorld.Tick驱动框架。再编写OnConnectClick和OnSendClick方法,分别对应界面上的按钮功能。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using GoWorldUnity3D;

public class echo : MonoBehaviour
{
 bool isConnected;
 public InputField inputField;
 // Start is called before the first frame update
 void Start()
    {
        Debug.Log("Register Entity Type Account ...");
        GoWorld.RegisterEntity(typeof(Account));
    }

 // Update is called once per frame
 void Update()
    {
 if (isConnected) {
            GoWorldUnity3D.GoWorld.Tick ();
        }
    }


 public void OnConnectClick(){
        GoWorldUnity3D.GoWorld.Connect("134.175.xxx.xxx", 14001);
        isConnected = true;
    }

 public void OnSendClick(){
        GoWorld.ClientOwner.CallServer ("Echo", inputField.text);
    }
}

OnConnectClick会连接服务端,OnSendClick会调用CallServer ,参数Echo意味着它会调用服务端Account实体的Echo_Client方法。

测试

编写完成后,绑定按钮事件,运行游戏。当成功连接服务端,会看到程序创建了个account对象。

当发送文本给服务端,会收到服务端的回应,并打印出来。

实现聊天室功能

接下来实现聊天室功能,当某个客户端发送一条消息,所有连接的客户端都应该收到这条消息。前面提及, 每个game在启动之后都会在本地创建一个唯一的NilSpace,但这个Space很特殊,无法交互。我们要另创建一个聊天Space,然后让Account转移到创建的Space中,新的Space即可以交互,如下图。

 

echo.go

为了记录新创建的Space,注册一个SpaceService服务。服务是一种特殊的实体,但它是全局唯一的。通过RegisterService注册即可,SpaceService稍后实现。

package main
 
import (
 "github.com/xiaonanln/goworld"
)
 
func main() {
    // 注册自定义的Space类型(必须提供)
    goworld.RegisterSpace(&MySpace{}) 
 // 注册Account类型
 goworld.RegisterEntity("Account", &Account{})
    // 注册自定义的SpaceService类型
    goworld.RegisterService("SpaceService", &SpaceService{})
 // 运行游戏服务器
 goworld.Run()
}

SpaceService.go

添加空间服务SpaceService,代码如下。当服务创建时,引擎会调用它的OnCreated方法,这里我们调用CreateSpaceAnywhere让程序在任意一个game中创建一个MySpace,并且把id记录到s.mySpaceID。编写另一个供实体RPC调用的方法GetSpaceID,参数callerID是实体的ID,它会调用实体的OnGetSpaceID方法,并把空的的id传给它。

package main
 
import (
    "github.com/xiaonanln/goworld"
    "github.com/xiaonanln/goworld/engine/gwlog"
    "github.com/xiaonanln/goworld/engine/common"
    "github.com/xiaonanln/goworld/engine/entity"
)
 
// SpaceService is the service entity for space management
type SpaceService struct {
 entity.Entity
    mySpaceID  common.EntityID
}
 
func (s *SpaceService) DescribeEntityType(desc *entity.EntityTypeDesc) {
}
 
// OnCreated is called when entity is created
func (s *SpaceService) OnCreated() {
 gwlog.Infof("Registering SpaceService ...")
    s.mySpaceID = goworld.CreateSpaceAnywhere(1)
    gwlog.Infof("s.mySpaceID = ", s.mySpaceID)
}
 
 
// 获取场景ID
func (s *SpaceService) GetSpaceID(callerID common.EntityID) {
    s.Call(callerID, "OnGetSpaceID", s.mySpaceID)
}

Account.go

修改代表玩家的Account.go,在它被创建时调用goworld.CallService("SpaceService", "GetSpaceID", a.ID)去SpaceService请求MySpace的ID,SpaceService会调用Account的OnGetSpaceID方法,它会调用EnterSpace进入到空间中。EnterSpace第一个参数代表空间的ID,第二个参数代表坐标,这里设置为默认值(0, 0, 0)。另外修改Echo_Client方法,现在不只是回应一个客户端,而是遍历Space中的所有实体,都做出回应。

package main
 
import (
    "github.com/xiaonanln/goworld"
 "github.com/xiaonanln/goworld/engine/entity"
    "github.com/xiaonanln/goworld/engine/common"
)
 
// 玩家类型
type Account struct {
    // 自定义对象类型必须继承entity.Entity
 entity.Entity
}
 
// OnCreated 在Player对象创建后被调用
func (a *Account) OnCreated() {
    goworld.CallService("SpaceService", "GetSpaceID", a.ID)
}
 
// OnGetSpaceID is called by SpaceService
func (a *Account) OnGetSpaceID(spaceID common.EntityID) {
 // let account enter space with spaceID
 a.EnterSpace(spaceID, entity.Vector3{})
}
 
func (a *Account) Echo_Client(msg string) {
    msg = msg + " server echo"
 
    a.Space.ForEachEntity(  func(e *entity.Entity) {
        e.CallClient("ShowInfo", msg)
    })
 
}
 
func (a *Account) DescribeEntityType(desc *entity.EntityTypeDesc) {
 
}

编写完成后重新编译运行服务端即可测试,如下图开启多个客户端,连接后,在左侧客户端中输入消息,右侧客户端也能够收到消息。

通过本节,读者应该对goworld程序的编写方法有个大致的了解。

推荐些资料

笔者所著《Unity3D网络游戏实战(第2版)》是一本专门介绍如何开发多人网络游戏的实战书籍,手把手教你搭建网络框架,制作大型项目。

「同步」也是网络游戏开发的核心课题。玩家的位置和旋转需要同步给其他玩家,然而网络条件差,会不同步和卡顿。笔者主讲的live《网络游戏同步算法》揭示做好同步的方法,欢迎收听。

网络游戏同步算法​www.zhihu.com图标

转自https://zhuanlan.zhihu.com/p/67951379 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值