一、介绍
本篇要讲的内容是在Unity中怎么实现多个客户端用户能操控同一个物体,如一个Cube,这个Cube在任何客户端有,相当于一个游戏的NPC,客户端A可以控制Cube的移动、旋转,Cube在A的位置或旋转的改变会同步到其他的客户端B、C等。同样,在其他客户端B、C控制Cube的位置和旋转也可以同步到A。如图所示:A点击LANHost(H)既做为服务器也作为客户端,B和C为客户端,鼠标点击哪个客户端就表示正在控制当前客户端的
Cube。通过不同客户端控制Cube,Cube的旋转和位移都会被同步到其他客户端。
二、实现(填坑)
1、思路(把坑想的比较理想):服务器生成Cube,这样保证每个客户端都能看到,然后在每个客户端直接控制Cube。
2、实现(直接就跳进去坑):
1>创建一个Cube,取名为”CubeNPC“,然后给这个Cube添加Network Identity和Network Transform两个组件,如图所示:然后,将这个Cube拖到Projects文件里,作为预设体,并删除场景中的这个Cube。
2>:创建一个空的物体,取名为“NetWorkManager",然后,添加NetworkManager和NetworkManagerHud两个组件,Network有了这两个组件后将作为一个管理器,统筹管理所有的网络状态和物体。然后将上面的CubeNPC拖入到Registered Spawnable Prefab里面,如图所示:这样次CubeNPC就可以注册成为一个网络物
体了,接下来是要在服务器端先生成。
3>:创建一个空物体,取名为”PlayerSpawner“,添加Network Identity组件,并勾选Server Only。这个物体就是用来专门生成类似于NPC的物体,因此只能在服务器端执行,如果是客户端,这个物体会被失活。
4>:创建生成CubeNPC的脚本,取名为PlayerSpawner,代码如下:在OnStartServer方法里生成CubeNPC
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;
public class PlayerSpawner : NetworkBehaviour
{
public static PlayerSpawner M_Instance
{
get
{
if(null==_instance)
{
_instance = GameObject.FindObjectOfType<PlayerSpawner>();
}
return _instance;
}
}
private static PlayerSpawner _instance;
public GameObject prefabCubeNPC;
[HideInInspector]
public GameObject ObjCubeNPC;
// Use this for initialization
void Start()
{
}
// Update is called once per frame
void Update()
{
}
public override void OnStartServer()
{
ObjCubeNPC = Instantiate(prefabCubeNPC);
ObjCubeNPC.transform.position = Vector3.zero;
ObjCubeNPC.transform.eulerAngles = Vector3.zero;
NetworkServer.Spawn(ObjCubeNPC);
}
}
5>:然后添加CubeNPC的控制脚本,简单点,直接控制位置和旋转,代码如下:但是,发现好像这个控制只能在服务器端执行的时候会同步到其他客户端,而客户端执行的时候只能在本机上改变,服务器端和其他客户端并没
private void Move()
{
rotY = Input.GetAxis("Horizontal") * Time.deltaTime * 50;
posZ = Input.GetAxis("Vertical") * Time.deltaTime * 50;
transform.Rotate(0, rotY, 0);
transform.Translate(0, 0, posZ);
}
有同步数据,如图所示:A仅仅作为服务器,B作为客户端,单独控制B的Cube,并不会影响A的Cube,也即没有同步数据到服务器,但是控制A中的Cube,B的状态立马恢复到A中Cube的数据状态,接下来保持状态同步。
这是因为,CubeNCP是服务器生成的物体,在其他客户端并没有权限通过控制来同步数据,另外服务器生成的这个Cube物体,其他客户端并没有权限发送Command命令,会提示如图所示的警告信息:
没有权限,所以在更改这个网络物体相应的位置和旋转后并不能同步到服务器和其他客户端上,只是本机上能看到改变,并且不能通过发送Command命令来同步信息。既然服务器端生成的这个CubeNPC,其他客户端没有权限,那要怎样才能获取这个权限呢。后来发现系统创建的Player是自带这个权限的,因此可以通过在Player的控制脚本里面发送这个Command脚本命令。
3、新思路(新的理想主义坑):通过每个客户端自动生成的Player,来发送Command命令,达到控制CubeNPC的目的。
4、新的实现
1>:创建一个空物体,命名为Player,如图所示,和CubeNPC一样,添加Network Identity和NetworkTransform组件,并将Player托到Project中作为预设体,删除场景总的Player。Player的代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.UI;
public class PlayerController : NetworkBehaviour {
private float rotY;
private float posZ;
[SerializeField]
private GameObject prefabSubObj;
//[SerializeField]
private GameObject subObj;
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update()
{
if (!isLocalPlayer)
{
return;
}
//if (Input.GetKeyDown(KeyCode.R))
//{
// CmdSpawaner();
//}
rotY = Input.GetAxis("Horizontal") * Time.deltaTime * 50;
posZ = Input.GetAxis("Vertical") * Time.deltaTime * 50;
if (Input.anyKey)
{
CmdControllerNPC2(rotY, posZ);
}
}
//[Command]
//private void CmdSpawaner()
//{
// subObj = Instantiate(prefabSubObj);
// subObj.transform.position = transform.position + new Vector3(0, 1.0f, 0);
// subObj.transform.eulerAngles = Vector3.zero;
// NetworkServer.SpawnWithClientAuthority(subObj, connectionToClient);
//}
[Command]
private void CmdControllerNPC2(float ry,float pz)
{
if (null != PlayerSpawner.M_Instance.ObjCubeNPC)
{
PlayerSpawner.M_Instance.ObjCubeNPC.transform.Rotate(0, ry, 0);
PlayerSpawner.M_Instance.ObjCubeNPC.transform.Translate(0, 0, pz);
}
}
}
修改PlayerSpawner的脚本为:,虽然这个脚本的物体只能在服务器上激活,但是上面Player的代码里的Command命令下的方法也是会在服务器端执行,因此不必担心非空的问题。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;
public class PlayerSpawner : NetworkBehaviour
{
public static PlayerSpawner M_Instance
{
get
{
if(null==_instance)
{
_instance = GameObject.FindObjectOfType<PlayerSpawner>();
}
return _instance;
}
}
private static PlayerSpawner _instance;
public GameObject prefabCubeNPC;
[HideInInspector]
public GameObject ObjCubeNPC;
// Use this for initialization
void Start()
{
}
// Update is called once per frame
void Update()
{
}
public override void OnStartServer()
{
ObjCubeNPC = Instantiate(prefabCubeNPC);
ObjCubeNPC.transform.position = Vector3.zero;
ObjCubeNPC.transform.eulerAngles = Vector3.zero;
NetworkServer.Spawn(ObjCubeNPC);
}
}
2>:在NetworkManager的SpawnInfo中添加Player预设体,并勾选Auto Create Player,如图所示:
在这里我删除了原来的NeworkManager组件,改用一个继承NetworkManager的Global脚本,因为NetworkManager是个单例,整个场景中就只能有一个。采用Global脚本,是想在后续获取一些脚本的控制。至此,解决了多个客户端对同一个物体的操作的问题。
三、总结
1、单纯服务器创建的CubeNPC只能在服务器端控制并同步数据到其他客户端,其他任何客户端都只能在本机上操作,不能同步数据到服务器端和其他客户端。并且客户端也不能在CubeNPC的脚本里使用Command命名来达到数据同步到服务器的控制,因为没有权限。
2、创建的Player对象有权限,在它的控制脚本里可以使用Command命令来操作CubeNPC的位置和旋转。
3、CubeNPC是否能单独授权给客户端呢?
答:可以的,采用NetworkServer.SpawnWithClientAuthority(subObj, connectionToClient);和
CubeNPC.GetComponent<NetworkIdentity>().AssignClientAuthority(connectionToClient);两个方法。前者是在创建了Player以后再在Player里面创建一个物体,并且将这个物体付给客户端一个权限,并且只能该客户端操作,其他的客户端还是不能操作。后者,还没有研究透怎么使用