在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 方法的其余部分(通常由 所有 玩家执行)
这样做可以让事情变得更简单、更整洁 - 至少对我来说是这样。
希望对您有所帮助
我有一个简单的 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 方法的其余部分(通常由 所有 玩家执行)
这样做可以让事情变得更简单、更整洁 - 至少对我来说是这样。
希望对您有所帮助