在Unity3D联网中,如何有"more than one Player"?

In Unity3D networking, how to have "more than one Player"?

我有一个简单的 LAN 拓扑,其中有一个服务器和三个客户端,

          -client
server    -client
          -client

(他们不是字面上的"players")

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

然而他们是"all the same"。同样的几个电话。

(服务器根据ID知道是哪一个发送的信息。)

起初,我只是生成了一个 "player" 对象,上面有一个带有各种 [Command] 调用的脚本。


每个客户端都有一个抽象的播放器对象


它由 NetworkManager 以通常的方式产生

但是

原来,服务器只有"believes"在最后一个连接。

Unity联网好像只能有一个"player"

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

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

  • 关于网络自动生成“玩家”预制件。

事实上,你会得到每个玩家一个。 (一说就明白了。)到处都会有一大堆这样的东西。

所以在这里:

          -client
server    -client
          -client

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

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

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

所以当你启动应用程序时,只有一台 PC,你将它设为服务器,还没有连接..

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

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

..等等

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

每个“玩家”都有自己的自动生成的玩家对象 - 并且在所有设备上都有。五个客户端,每个 设备(所有六台设备)上将有五个自动生成的播放器对象。

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

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

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

事实上,你必须找到你的 'own' 播放器预制件....

您必须找到您的 'own' 播放器预制件....

你可能会想到有这样的代码..

// 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”。

在大多数 unity 示例中,他们设想您“在那个脚本中”做事。

那样的话就更直观了:如果你不是.isLocalPlayer

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

事实上,

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

棘手!

重复 - Unity doco 示例倾向于强调“在该脚本上”做事。实际上,在任何大型现实世界项目中都不是这样,您正在“联系”该脚本作为中央通信资源。

不是一个完整的答案,因为你已经自己想出来了。

(您没有最小的代码示例,所以老实说,我没有完全理解您在做什么...但是)
不管怎样,我想分享一下我通常是怎么做的。

在 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 布尔值也一样 isServer

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

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

    // ... do your stuff
}

现在,对于仅向玩家传达某些内容的部分,如何称呼某些内容:

不幸的是,您不能将 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 方法的其余部分(通常由 所有 玩家执行)

这样做可以让事情变得更简单、更整洁 - 至少对我来说是这样。

希望对您有所帮助