1. 程式人生 > >Unity工程案例—多客戶端控制同一個物體

Unity工程案例—多客戶端控制同一個物體

一、介紹

    本篇要講的內容是在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裡面建立一個物體,並且將這個物體付給客戶端一個許可權,並且只能該客戶端操作,其他的客戶端還是不能操作。後者,還沒有研究透怎麼使用