1. 程式人生 > >使用Multiplayer Networking做一個簡單的多人遊戲例子-2/3(Unity3D開發之二十六)

使用Multiplayer Networking做一個簡單的多人遊戲例子-2/3(Unity3D開發之二十六)

7. 在網路中控制Player移動

上一篇中,玩家操作移動會同時控制同屏內的所有Player,且只有自己的螢幕生效。因為咱們還沒有同步Transform資訊。
下面我們通過UnityEngine.Networking元件來實現玩家控制各自Player

  • 開啟PlayerController指令碼
  • 新增名稱空間UnityEngine.Networking
using UnityEngine.Networking;
  • 修改MonoBehaviour為NetworkBehaviour
public class PlayerController : NetworkBehaviour
  • 在Update函式中新增如下方法
if (!isLocalPlayer)
{
    return;
}

最後你的PlayerController內容如下:

using UnityEngine;
using UnityEngine.Networking;

public class PlayerController : NetworkBehaviour
{
    void Update()
    {
        if (!isLocalPlayer)
        {
            return;
        }

        var x = Input.GetAxis("Horizontal"
) * Time.deltaTime * 150.0f; var z = Input.GetAxis("Vertical") * Time.deltaTime * 3.0f; transform.Rotate(0, x, 0); transform.Translate(0, 0, z); } }
  • 儲存指令碼
  • 回到Unity中
  • 選中Player prefab在Project面板中
  • 保持Player prefab為選中狀態
  • 新增元件Network > NetworkTransform
  • 儲存工程
    這裡寫圖片描述

NetworkTransform用於在網路中同步所有Client資訊。加上isLocalPlayer判斷,只讓當前客戶端操作。

8. 測試網路中多玩家移動

  • 同樣Build一個Mac standalone application作為主機執行
  • 點選LAN Host作為主機開始遊戲
  • 執行Unity,點選LAN Client作為另一個客戶端加入遊戲
  • 點選各自的WASD移動各自Player

9. 區分各自的Player

上面中兩個Player外觀一致,我們修改自己Player的顏色

  • 開啟PlayerController指令碼
  • 新增OnStartLocalPlayer方法
public override void OnStartLocalPlayer()
{
    GetComponent<MeshRenderer>().material.color = Color.blue;
}

最終PlayerController:

using UnityEngine;
using UnityEngine.Networking;

public class PlayerController : NetworkBehaviour
{
    void Update()
    {
        if (!isLocalPlayer)
        {
            return;
        }

        var x = Input.GetAxis("Horizontal") * Time.deltaTime * 150.0f;
        var z = Input.GetAxis("Vertical") * Time.deltaTime * 3.0f;

        transform.Rotate(0, x, 0);
        transform.Translate(0, 0, z);
    }

    public override void OnStartLocalPlayer()
    {
        GetComponent<MeshRenderer>().material.color = Color.blue;
    }
}
  • Build新的Mac版本,測試效果
    這裡寫圖片描述
  • 關閉Mac版本,停止執行Unity,回到編輯狀態

10. 給Player新增射擊武器

建立子彈

  • 建立一個Sphere GameObject
  • 修改名稱為“Bullet”
  • 選中Bullet物件
  • 修改Transform (0.2, 0.2, 0.2)
  • 新增元件Physics > Rigidbody
  • 在Rigidbody屬性中取消Use Gravity
  • 拖拽Bullet到Project面板中,製作為Prefab
  • 刪除場景中Bullet
  • 儲存場景

下面修改PlayerController添加發射子彈

  • 開啟PlayerController指令碼
  • 新增public變數bulletPrefab
public GameObject bulletPrefab;
  • 新增子彈local發射點
public Transform bulletSpawn;
  • 在Update中加入輸入源
if (Input.GetKeyDown(KeyCode.Space))
{
    Fire();
}
  • 新增Fire方法
void Fire()
{
    // Create the Bullet from the Bullet Prefab
    var bullet = (GameObject)Instantiate (
        bulletPrefab,
        bulletSpawn.position,
        bulletSpawn.rotation);

    // Add velocity to the bullet
    bullet.GetComponent<Rigidbody>().velocity = bullet.transform.forward * 6;

    // Destroy the bullet after 2 seconds
    Destroy(bullet, 2.0f);
}

最終PlayerController如下:

using UnityEngine;
using UnityEngine.Networking;

public class PlayerController : NetworkBehaviour
{
    public GameObject bulletPrefab;
    public Transform bulletSpawn;

    void Update()
    {
        if (!isLocalPlayer)
        {
            return;
        }

        var x = Input.GetAxis("Horizontal") * Time.deltaTime * 150.0f;
        var z = Input.GetAxis("Vertical") * Time.deltaTime * 3.0f;

        transform.Rotate(0, x, 0);
        transform.Translate(0, 0, z);

        if (Input.GetKeyDown(KeyCode.Space))
        {
            Fire();
        }
    }


    void Fire()
    {
        // Create the Bullet from the Bullet Prefab
        var bullet = (GameObject)Instantiate(
            bulletPrefab,
            bulletSpawn.position,
            bulletSpawn.rotation);

        // Add velocity to the bullet
        bullet.GetComponent<Rigidbody>().velocity = bullet.transform.forward * 6;

        // Destroy the bullet after 2 seconds
        Destroy(bullet, 2.0f);        
    }

    public override void OnStartLocalPlayer ()
    {
        GetComponent<MeshRenderer>().material.color = Color.blue;
    }
}
  • 儲存指令碼
  • 回到Unity

下面開始針對變化過的PlayerController修改Player Prefab

  • 將Player Prefab拖拽到場景中
  • 保持Player prefab選中
  • 建立一個Cylinder圓柱體作為其Child
  • 修改Cylinder名稱為“Gun”
  • 保持Gun被選中
  • 移除Capsule Collider元件
  • 設定Transform Position (0.5, 0.0, 0.5)
  • 設定Transform Rotation (90.0, 0.0, 0.0)
  • 設定Transform Scale (0.25, 0.5, 0.25)
  • 設定Material為Black Material

最後Player效果:
這裡寫圖片描述

  • 保持Player選中狀態
  • 建立一個empty GameObject作為Child
  • 修改empty GameObject名稱為“Bullet Spawn”
  • 設定Bullet Spawn Position (0.5, 0.0, 1.0)

主要是將子彈發射點Bullet Spawn設定到槍口處
這裡寫圖片描述

  • 保持Player Prefab選中
  • 將Bullet prefab拖到PlayerController中的Bullet Prefab 框
  • 將Player的Child Bullet Spawn拖到PlayerController中的Bullet Spawn 框
  • 儲存工程
  • Build新的Mac版本,並測試

你會發現空格鍵可以在各自場景中發射子彈,但是子彈沒有出現在對方場景中。

  • 關閉Mac版本
  • 停止Unity,回到編輯模式

11. 增加多人射擊

下面我們會將Bullet prefab註冊到NetworkManager

  • 選中Bullet prefab在Project面板中
  • 儲存Bullet prefab選中
  • 新增元件Network > NetworkIdentity
  • 新增元件Network > NetworkTransform
  • 設定NetworkTransform中Network Send Rate為0

子彈不會中途改變方向,所以我們不需要每幀更新位置,每個客戶端自己計算Bullet座標資訊,所以將Network Send Rate設定為0,網路不需要同步座標資訊。
這裡寫圖片描述

  • 選中NetworkManager在Hierarchy面板中
  • 保持NetworkManager選中
  • 展開Spawn Info
  • 點選Registered Spawnable Prefabs右下角+
  • 將Bullet Prefab加入到Registered Spawnable Prefabs中
    這裡寫圖片描述

  • 開啟PlayerController指令碼

注意[Command]可以宣告一個函式可以本客戶端呼叫,但是會在服務端(主機)執行。

  • 新增[Command]給Fire函式
  • 修改Fire函式名稱為“CmdFire”
[Command]
void CmdFire()
  • Update函式中修改呼叫為CmdFire
CmdFire();
  • 在CmdFire函式中新增NetworkServer.Spawn方法來建立bullet
NetworkServer.Spawn(bullet);

最終PlayerController如下:

using UnityEngine;
using UnityEngine.Networking;

public class PlayerController : NetworkBehaviour
{
    public GameObject bulletPrefab;
    public Transform bulletSpawn;

    void Update()
    {
        if (!isLocalPlayer)
        {
            return;
        }

        var x = Input.GetAxis("Horizontal") * Time.deltaTime * 150.0f;
        var z = Input.GetAxis("Vertical") * Time.deltaTime * 3.0f;

        transform.Rotate(0, x, 0);
        transform.Translate(0, 0, z);

        if (Input.GetKeyDown(KeyCode.Space))
        {
            CmdFire();
        }
    }

    // This [Command] code is called on the Client …
    // … but it is run on the Server!
    [Command]
    void CmdFire()
    {
        // Create the Bullet from the Bullet Prefab
        var bullet = (GameObject)Instantiate(
            bulletPrefab,
            bulletSpawn.position,
            bulletSpawn.rotation);

        // Add velocity to the bullet
        bullet.GetComponent<Rigidbody>().velocity = bullet.transform.forward * 6;

        // Spawn the bullet on the Clients
        NetworkServer.Spawn(bullet);

        // Destroy the bullet after 2 seconds
        Destroy(bullet, 2.0f);
    }

    public override void OnStartLocalPlayer ()
    {
        GetComponent<MeshRenderer>().material.color = Color.blue;
    }
}
  • 儲存指令碼
  • 回到Unity
  • Build新版本Mac,測試

此時應該可以看到子彈同步到了每個玩家場景中

  • 關閉Mac版本
  • 停止執行Unity,回到編輯模式

新增玩家生命值

  • 給Bullet Prefab新增一個新指令碼“Bullet”
  • 開啟Bullet指令碼
  • 新增碰撞函式
using UnityEngine;
using System.Collections;

public class Bullet : MonoBehaviour {

    void OnCollisionEnter(Collision collision)
    {
        Destroy(gameObject);
    }
}

此時子彈碰撞到Player之後自動銷燬

新增玩家生命值
- 給Player prefab新增一個新指令碼“Health”
Health指令碼如下:

using UnityEngine;

public class Health : MonoBehaviour 
{
    public const int maxHealth = 100;
    public int currentHealth = maxHealth;

   public void TakeDamage(int amount)
    {
        currentHealth -= amount;
        if (currentHealth <= 0)
        {
            currentHealth = 0;
            Debug.Log("Dead!");
        }
    }
}
  • 儲存指令碼

在Bullet中增加擊中受傷

  • 修改Bullet中的OnCollisionEnter函式
using UnityEngine;
using System.Collections;

public class Bullet : MonoBehaviour {

    void OnCollisionEnter(Collision collision)
    {
        var hit = collision.gameObject;
        var health = hit.GetComponent<Health>();
        if (health  != null)
        {
            health.TakeDamage(10);
        }

        Destroy(gameObject);
    }
}

新增一個簡易的玩家頭頂血條

  • 在場中中建立一個UI Image
  • 修改Canvas名稱為“Healthbar Canvas”
  • 修改Image名稱為“Background”
  • 儲存Background選中
  • 設定RectTransform Width 100
  • 設定RectTransform Height 10
  • 修改Source Image 為 built-in InputFieldBackground
  • 修改 Image Color 為紅色
  • 不要修改Background的中心點和錨點
  • 複製一份Background
  • 修改複製出來的Background名稱為Foreground
  • 將Foreground設定為Background的Child
  • 將Player prefab拖到場景中
  • 將Healthbar Canvas拖到Player中作為Child
    整個Player結構如下:
    這裡寫圖片描述

  • 選中Foreground

  • 設定Foreground Image為綠色
  • 將Foreground 中心點和錨點修改為Middle Left(用於血條從左到右填充)
    這裡寫圖片描述

  • 選中Healthbar Canvas

  • 點選RectTransform設定按鈕(小齒輪)中的Reset
  • 設定RectTransform Scale (0.01, 0.01, 0.01)
  • 設定RectTransform Position (0.0, 1.5, 0.0)
  • 選中Player,點選apply,儲存Player Prefab
  • 儲存場景

修改Health指令碼,控制血條
最終Health指令碼如下:

using UnityEngine;
using UnityEngine.UI;
using System.Collections;

public class Health : MonoBehaviour {

    public const int maxHealth = 100;
    public int currentHealth = maxHealth;
    public RectTransform healthBar;

    public void TakeDamage(int amount)
    {
        currentHealth -= amount;
        if (currentHealth <= 0)
        {
            currentHealth = 0;
            Debug.Log("Dead!");
        }

        healthBar.sizeDelta = new Vector2(currentHealth, healthBar.sizeDelta.y);
    }
}
  • 儲存指令碼
  • 回到Unity
  • 保持Player選中
  • 將Foreground拖到Healthbar輸入框中
    這裡寫圖片描述
  • apply Player prefab
  • 刪除場景中的Player
  • 儲存場景

最後,修改Healthbar永遠朝向主攝像機
- 給Player prefab中的Healthbar Canvas新增新指令碼“Billboard”
Billboard指令碼如下:

using UnityEngine;
using System.Collections;

public class Billboard : MonoBehaviour {

    void Update () {
        transform.LookAt(Camera.main.transform);
    }
}
  • 編譯新Mac版本,測試

你會發現血條只在本地變化了,沒有同步到所有玩家。