1

我有一个带有服务器的简单 LAN 拓扑,并说三个客户端,

          -client
server    -client
          -client

(他们不是字面上的“玩家”)

三个客户端向服务器传递各种信息。当然只是使用[Command]调用。

然而,它们“都一样”。同样的几个电话被打了。

(服务器从 ID 知道是哪一个发送了信息。)

起初,我只是简单地生成了一个“玩家”对象,上面有一个带有各种[Command]调用的脚本。

在此处输入图像描述
每个客户端都有一个抽象的播放器对象

在此处输入图像描述
它由 NetworkManager 以通常的方式生成

然而。

事实证明,服务器只“相信”最后一个连接的服务器。

似乎 Unity 网络中只能有一个“玩家”。

真的,您如何在 Unity 网络中做到这一点?

4

2 回答 2

2

回答我自己的问题,对于任何为此苦苦挣扎的人:

  • 关于网络自动生成“播放器”预制件。

实际上,您将为每个玩家获得一个。(一旦你说出来似乎很明显。)到处都是这些。

所以在这里:

          -client
server    -client
          -client

事实上,你会看到三个生成的预制件。

(除此之外:在我的示例中,我将服务器用作“服务器”,而不是“主机”..

在此处输入图像描述

如果您使用的是“主机”概念,那么那里还会有另一个生成的播放器预制件。)

因此,当您启动应用程序时,只有一台 PC,您将其设置为服务器,还没有连接..

在此处输入图像描述

然后你启动第二台电脑,第一个客户端,他们连接......现在看起来像这样

在此处输入图像描述

然后你启动第三台 PC,第二个客户端,他们连接......现在看起来像这样

在此处输入图像描述

.. 等等。

因此,每个客户端都有一个克隆的播放器项目。(同样,如果您对服务器使用“主机”概念,那仅意味着在与服务器相同的物理盒子上会有另一个客户端。)

每个“玩家”都有自己的自动生成的玩家对象——而且在所有设备上五个客户端,每个设备(在所有六个设备上)上都会有五个自动生成的玩家对象。

那么是什么导致了提到的特定错误?

Unet 中隐藏的令人惊讶的困难是.isLocalPlayer概念。

假设您Comms在播放器预制件上确实有示例中的脚本,这似乎是一件很自然的事情。假设游戏的其他部分必须联系 Comms(例如向服务器发送消息)。

事实上,你必须找到你自己的播放器预制......

你必须找到你自己的播放器预制件......

您可能会考虑编写类似这样的代码..

// laser blows up
// tell Comms to send a message to the server about that
// find Comms ..
Comms co = FindObjectOfType<Comms>();
// tell it to send the message
co.TellServerLaserBlewUp();

然而这是错误的,调试将非常不稳定。

实际上,您的代码如下所示:

// laser blows up
// tell Comms to send a message to the server about that
// find >> OUR << Comms ..
foreach (Comms co in FindObjectsOfType<Comms>() ) {

    string ilp = co.isLocalPlayer ? "YY" : "NN";
    Debug.Log("I tried one of the many Comms! .. " + ilp);
    if (co.isLocalPlayer) {
        
         Debug.Log("Found it!");
         co.TellServerLaserBlewUp();
    }
}

(注意 - 显然,你不会在生产中每次都找到这样的东西,你会使用单例或任何你的茶。)

考虑“在”播放器预制件上的脚本。在我们的示例中,它是“Comms.cs”。

在大多数统一示例中,他们设想您“在该脚本中”做一些事情。

在这种情况下,它更直观:如果你不是,你就离开.isLocalPlayer

然而,这是非常幼稚的,实际上你在场景中的无数脚本会想要/需要联系你的“Comms.cs”并让它做一些事情(因为它是唯一连接到网络的脚本。

实际上,

您必须.isLocalPlayer始终找到“您的”。

棘手!

重复一遍 - Unity doco 示例倾向于强调“在那个脚本上”做事。在任何大型现实世界项目的实践中,情况并非如此,您正在“联系”该脚本作为中央通信资源。

于 2018-08-09T05:16:39.457 回答
1

不是一个完整的答案,因为您已经自己弄清楚了。

(你没有一个最小的代码示例,所以说实话,我并不完全遵循你在做什么......但是)
无论如何我想分享我通常是如何做的。

在 Player 预制件上(在连接时生成)我放置了一个特殊组件,例如

using UnityEngine;
using UnityEngine.Networking;

[DisallowMultipleComponent]
[RequireComponent(typeof(NetworkIdentity))]
public class PlayerInformation : NetworkBehaviour
{
    // simply a public accessor for isLocalPlayer
    public bool IsLocalPlayer
    {
        get {return isLocalPlayer;}
    }
}

如果需要对其他 NetworkBehaviour boolsisServer等进行相同的操作。

每当我必须从另一个组件调用方法时,我也会将它交给PlayerInformation
-> 我可以直接检查它是否是本地播放器,例如

public void DoSomething(PlayerInformation player)
{
    // don't even go any further if not local player
    if(!player.IsLocalPlayer) return;

    // ... do your stuff
}

现在对于将某些东西返回给玩家的部分,它是如何被称为某物的:

不幸的是,您不能将 a 传递Component给命令和 Rpc .. 但您可以传递GameObjects(具有 NetworkIdentity)。NetworkIdentity 确保可以在所有连接的实例上识别 GameObject。

// This time this is called by the client
[Client]
private void DoSomething()
{
    if(!isLocalPlayer) return;

    // Here we call the command on the server passing our gameObject
    CmdDoSomething(gameObject);
}

// This is executed on the server
// Since the player has a NetworkIdentity the server can identify the correct gameObject we passed
[Command]
private void CmdDoSomething (GameObject player)
{
    // Now we can send back Information
    // E.g. an int or string again passing the GameObject
    RpcDoSomething(player, "test");
}

// Usually this would be executed by ALL clients
// So we have to check if we are the player who originally called the command
// Again the NetworkIdentity makes sure we can identify the passed GameObject
[ClientRpc]
private void RpcDoSomething (GameObject player, string message)
{
    // If we are not the one who invoked the command we do nothing
    if(player != gameObject) return;

    // I like to put Debug.Log containing "this" in the end so you can click a Log
    // Entry and see exactly where it came from
    Debug.Log("I received: " + message, this);
}

现在只有调用命令的玩家应该执行 Rpc 方法的其余部分(通常由所有玩家执行)

这样做可以让事情变得更轻松、更整洁——至少对我来说是这样。

希望能帮助到你

于 2018-08-09T08:53:37.957 回答