2

我想将网络代码与我的游戏逻辑分开。我不仅需要这样做才能在单人和多人游戏模式之间共享游戏逻辑,而且因为关注点分离的事情,我也想要它。

我目前的方法是为我的网络相关类生成代码,以便有在线和离线版本。我使用 T4 模板执行此操作。

生成的类如下所示:

单机版/单机版:

// T4 GENERATED CODE
// Head (Singleplayer version)
class StandaloneHelloWorld : MonoBehaviour, IHelloWorld
{
    private string name;

    public void SayHello()
    {
        SayHelloInternal();
    }

// Body
    public string Name
    {
        get { return name; }
        set { name = value; }
    }

    void SayHelloInternal()
    {
        Debug.Log(Name + ": Hello World");
    }
}

多人版:

// T4 GENERATED CODE
// Head (Multiplayer version)
class NetworkedHelloWorld : NetworkBehaviour, IHelloWorld
{
    [SyncVar]
    private string name;

    public void SayHello()
    {
        CmdSayHello();
    }

    [Command]
    void CmdSayHello()
    {
        RpcSayHello();
    }

    [ClientRpc]
    void RpcSayHello()
    {
        SayHelloInternal();
    }

// Body
    public string Name
    {
        get { return name; }
        set { name = value; }
    }

    void SayHelloInternal()
    {
        Debug.Log(Name + ": Hello World");
    }
}

它们都共享一个接口来向调用者隐藏实现:

interface IHelloWorld
{
    string Name { get; set; }
    void SayHello();
}

如您所见,两种实现都使用相同的主体,共享大部分代码,而入口点取决于实现是否联网。另请注意,这两个实现继承了不同的基类。

优点:

  • 单人代码对网络代码没有依赖性,反之亦然
  • 没有重复的代码(至少不需要手动维护)

缺点:

  • Unity 中对接口的支持是有限的。我将无法从编辑器中引用 IHelloWorld 的场景实例。
  • 必须为单人/多人游戏模式维护单独的 Prefab
  • 不得不干预 T4/代码生成

你知道更好的方法来解决这个问题吗?你是如何解决这个问题的?

4

1 回答 1

1

您可以以基于事件的方式构造代码。这将允许系统注册他们感兴趣的事件。这自然地将逻辑与网络代码分开。

例如,假设您要发射弹丸。你可以通过调用来触发它:

new Event(EventType.FireProjectile, pos, dir, template)

然后,您可以注册对此事件感兴趣的系统:

CollisionSystem.Register(EventType.FireProjectile, (e) => {
  CollisionSystem.AddCollider(e.template.bounds); 
});

AudioSystem.Register(EventType.FireProjectile, (e) => {
  AudioSystem.PlaySound("Woosh"); 
});

AISystem.Register(EventType.FireProjectile, (e) => {
  AISystem.AlertAtPosition(e.pos); 
});

接下来很酷的是,您可以将此事件注册到NetworkSystem将对其进行序列化、在网络上移动、反序列化并在客户端计算机上触发它的事件。因此,就客户而言,此事件是在本地调用的。

NetworkSystem.Register(EventType.FireProjectile, (e) => {
  NetworkSystem.Broadcast(e, Channel.Reliable); 
});

这非常棒,只是您很快就会意识到这将导致事件的无限循环。当您向其他客户端发送 FireProjectile 事件时,他们会捕获并触发它。他们的 NetworkSystem 立即捕捉到它并通过网络发射它。

要解决此问题,您需要为每个操作提供两个事件 - 一个 request:FireProjectile和 response: ProjectileFired

不久前,我曾在一个个人项目中使用过这样的代码库。它是用 C++ 编写的,但如果您有兴趣,可以在此处阅读更多内容。请注意服务器和客户端如何注册某些事件,它们将转发。

于 2017-05-05T13:06:34.237 回答