使用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版本,測試
你會發現血條只在本地變化了,沒有同步到所有玩家。