我将在本文中讲述我是如何使用unity创造一个多人多场景的3D应用的。我之所以没有把它称为“游戏”是因为我不想把这个应用变成一个射击、毁坏、赚钱或者是有其他游戏元素的地方。
那么这个应用的功能是:在有街道和建筑的3D场景中你可以直接与别人交流或者是在他们的家里留下信息。现在这很简单,但是将来我想加入语音、个性定制还有像学校、公司、组织或者会议大楼的不同类型的建筑。
开发须知
Unity是一个很棒的工具,你可以直接在3D场景中添加物体,还可以操作地形。每一个物体都可以加上不同的组件,这些组件可以是Unity本身已经存在的或是你自己实现的。在Unity中还有资源商店,你可以从资源商店中找到很多的3D物体和脚本。调试的部分也很有意思,运行程序时可以切换到场景视窗然后作出调整,结果会直接呈现在游戏窗口中。
我把Unity纳入考虑是因为我用.Net已经6年了,因此我很习惯用它。Unity也可以用Javascript开发,但是对我来说使用C#开发更容易。
网络
有许多关于Unity网络的文章,我不想对此太过深入,但是基本上总会有一个服务器负责处理应用之间的通信。可以选择应用中的一个作为服务器来处理服务器的操作,所有的客户端通过(RPC)远程程序调用与服务器通信。
Unity中的多场景多玩家
应用中现在有不同的街道,不同的建筑。因此用户可以到一条街道上与其他用户见面。因此我只需要把此用户的位置推送给在那条街道上的用户即可。那么,如何实现呢?Unity有NetworkView.group和SetLevelPrefix(),但是这没有帮我实现一个多场景多玩家的应用。这是最难实现的部分,因为我没有在任何地方找到如何在Unity中实现它。当然,你可以找到一些第三方插件。
Unity有NetworkView组件用来同步一个观察的组件。当所有玩家都在同一个场景中时这个组件可以用。但是当有很多的场景可以供玩家自由出入时,带有需要同步观察组件的NetworkView就不是一个好的选择了。因为NetworkView会把消息送给所有的场景。因此,第一件需要做的事情就是禁止NetworkView同步,用户只向处于相同场景中的用户发送同步消息。对于这个需求,服务器需要知道每一个玩家所处的场景。因此客户端的调用像下面的代码:
在客户端(将状态发送给服务器):
void Update()
{
if (lastLocalPlayerPosition != localPlayerObject.transform.position || lastLocalPlayerRotation != localPlayerObject.transform.rotation)
{
networkView.RPC("ServerUpdatePlayerPosition", RPCMode.Server, lastLocalPlayerPosition, lastLocalPlayerRotation, level);
}
}
在服务器端(过滤玩家,只向在同一场景的玩家发送更新)
[RPC]
void ServerUpdatePlayerPosition(Vector3 pos, Quaternion rot, string level)
{
foreach (PlayerProperties player in server_OnlinePlayers.Values)
{
if (player.Level == level && player.NetworkPlayer != Network.Player)
networkView.RPC("ClientUpdatePlayerPosition", player.NetworkPlayer, p, pos, rot);
}
}
在其他客户端:
[RPC]
void ClientUpdatePlayerPosition(NetworkPlayer p, Vector3 pos, Quaternion rot)
{
var thePlayer = FindPlayer(p);
if ( thePlayer != null && thePlayer.GameObject != null)
{
var controller = thePlayer.GameObject.GetComponent<ThirdPersonControllerC>();
controller.position = pos;
controller.rotation = rot;
}
}
一些技巧
如何通过RPC调用发送定制物体
Unity不允许通过RPC发送定制物体,只有int/string/float/NetworkPlayer/NetworkViewID/Vector/Quaternation类型的物体才可以被传送。为了发送不同种类的物体,你需要把此物体序列化和反序列化。首先,我使用strings来存储,但是存在一个string中存储大于4096个char的问题。然后我发现Unity也接受bytes数组,因此我使用了这个特性。
比如我通过RPC调用发送了一个UserMessage类型的物体,此物的属性有:Date,UserId,Message,Type等等。这个是不可能实现的,因为这个物体是不会被接受的,因此我用下面的方法把它序列化:
public static byte[] Byte_ConvertObjectToBinary(object obj)
{
MemoryStream o = new MemoryStream();
BinaryFormatter bf = new BinaryFormatter();
bf.Serialize(o, obj);
return o.GetBuffer();
}
然后通过RPC调用发送:
networkView.RPC("Server_NewMessageAdded", networkPlayer, Utils.Byte_ConvertObjectToBinary(userMessage ));
然后在客户端反序列化:
public static object Byte_ReadFromBinary(byte[] data)
{
if (data == null || data.Length == 0)
return null;
var ins = new MemoryStream(data);
BinaryFormatter bf = new BinaryFormatter();
return bf.Deserialize(ins);
}
如何把一个定制的物体附着到一个游戏物体上
(如果那个物体不是MonoBehaviour的子类):
我把一个空物体加到一个有TextMesh的物体上,因此我把有文字数字能够的物体序列化为一个string,当我需要物体时,我把这个物体反序列化然后再使用。
免费的3D模型
这个应用我需要一些3D模型,但是我不是一个3D建模师,因此我需要一些免费的模型。Sketchup是一个很好的工具,你可以找到和创造很多3D模型。在Unity资源商店中有许多的免费模型。
结论
Windows/Linux//BlackBerry/iOS/WindowsPhone 8/unity /XBOX/Wii.使用Unity工作很好,对我来说这是一个制作3D游戏的新体验。Unity另外一个很好的地方是Unity可以被发布到如下平台:
Windows/Linux/Android/BlackBerry/iOS/WindowsPhone 8/unity WebPlayer/XBOX/Wii。
原文链接:https://www.gamedev.net/resources/_/technical/multiplayer-and-network-programming/multilevel-multiplayer-with-unity-r3580
原文作者:Sorin Albu