一、創建場景。
命名場景爲Main
二、創建NetWorkManager空對象。
並添加腳本NetWorkManagerHUD
(
NetWorkManager
The High Level API
)
三、創建遊戲同步Player三維對象。
四、將Player預製體註冊到NetWorkManager上。
拖動Player到NetWorkManager的SpawnInfo的PlayerPrefab屬性上
五、爲Player添加運動操作腳本
using UnityEngine;
public class PlayerController : MonoBehaviour
{
void Update()
{
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);
}
}
六、測試本地以 LAN Host啓動遊戲。
七、實現只控制本地對象。
八、多人在線測試。
修改 Network Send Rate並再次測試,觀察流暢度
九、識別本地玩家。
在腳本中添加:
public
override
void
OnStartLocalPlayer()
{
GetComponent().material.color = Color.blue;
十、創建本地射擊功能。製作子彈預製體,並添加剛體組件,修改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;
}
},將子彈預製體和子彈生成點拖入到腳本上
十一、多人在線射擊。
在子彈上添加NetwordIdentity,NetworkTransform.並將NetWorkTranform中的NetworkSendRate
設置爲一,NetWorkIdentity上LocalPlayerAuthorit設置爲True.
此時進一步修改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;
}
}
十二,本地人物血量。
添加子彈邏輯代碼,實現碰撞後銷燬子彈
using UnityEngine;
using System.Collections;
public class Bullet : MonoBehaviour {
void OnCollisionEnter()
{
Destroy(gameObject);
}
}
添加人物血量腳本,並有受到攻擊掉血的方法。
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!");
}
}
}
當子彈和人物發生碰撞時,需要調用人物Heath腳本的TackDamage方法,修改子彈腳本爲:
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);
}
}
添加世界座標中血量條,增加一個3維canvas,作爲Player的子物體,命名爲HealthBarCanvas.將大小設置爲(0.01,0.01,0.01)座標設置爲(0,1.5,0).添加兩個Image分別命名爲Background和Foreground,顏色設置爲紅色和藍色,設置父子關係,將Forground的中心點設置爲(0,0.5)錨點設置爲(0,0.5)(0,0.5)。
修改Health腳本更新血量顯示
using
UnityEngine;
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);
}
}
記得將血條foreground賦予腳本。
血條需要看到攝像機才行,在血條上添加腳本Billboard
using UnityEngine;
using System.Collections;
public class Billboard : MonoBehaviour {
void Update () {
transform.LookAt(Camera.main.transform);
}
}
此時由於血量沒有進行同步,子彈雖然在每個客戶端裏者存在,但由於碰撞的不同血量可能會完全不同
十三、血量同步
修改Health腳本修改爲掉血只在服務端運行,然後通過 [SyncVar]
來進行變量同步到客戶端。
using
UnityEngine;
using
UnityEngine.UI;
using
UnityEngine.Networking;
using
System.Collections;
public
class
Health
:
NetworkBehaviour
{
public
const
int
maxHealth = 100;
[SyncVar]
public
int
currentHealth = maxHealth;
public
RectTransform
healthBar;
public
void
TakeDamage(int
amount)
{
if
(!isServer)
{
return;
}
currentHealth -= amount;
if
(currentHealth <= 0)
{
currentHealth = 0;
Debug.Log("Dead!");
}
healthBar.sizeDelta =
new
Vector2(currentHealth, healthBar.sizeDelta.y);
}
}
當作服務端的客戶端運行時可以看到血量的變化,因爲currentHealth雖然同步了,但並沒有觸發血量變化的事件。利用 SyncVar hook 可以在變量發生變化後,同步到UI.
Health腳本修改爲如下 :
十四,重生
在服務端調用 ClientRpc 同步到客戶端(和Command相反)
實現當血量小於0時,在原點滿血復活效果(如果服務端僅僅將某個Player重置到原點,客戶端會因爲NetWorkTransform重寫這個Player的座標)
using
UnityEngine;
using
UnityEngine.UI;
using
UnityEngine.Networking;
using
System.Collections;
public
class
Health
:
NetworkBehaviour
{
public
const
int
maxHealth = 100;
[SyncVar(hook
=
"OnChangeHealth")]
public
int
currentHealth = maxHealth;
public
RectTransform
healthBar;
public
void
TakeDamage(int
amount)
{
if
(!isServer)
return;
currentHealth -= amount;
if
(currentHealth <= 0)
{
currentHealth = maxHealth;
// called on the Server, but invoked on the Clients
RpcRespawn();
}
}
void
OnChangeHealth(int
currentHealth)
{
healthBar.sizeDelta =
new
Vector2(currentHealth, healthBar.sizeDelta.y);
}
[ClientRpc]
void
RpcRespawn()
{
if
(isLocalPlayer)
{
// move back to zero location
transform.position =
Vector3.zero;
}
}
}
十四,非Player對象的同步
非Player的遊戲對象由Server來控制,下面以ai敵人爲例。
創建一個EnemySpawner空物體,NetworkIdentity
設置
Server Only爲True,添加EnemySpawner
腳本來隨機生成敵人
,重寫
OnStartServer方法
using
UnityEngine;
using
UnityEngine.Networking;
public
class
EnemySpawner
:
NetworkBehaviour
{
public
GameObject
enemyPrefab;
public
int
numberOfEnemies;
public
override
void
OnStartServer()
{
for
(int
i = 0; i < numberOfEnemies; i++)
{
var
spawnPosition =
new
Vector3(
Random.Range(-8.0f, 8.0f),
0.0f,
Random.Range(-8.0f, 8.0f));
var
spawnRotation =
Quaternion.Euler(
0.0f,
Random.Range(0, 180),
0.0f);
var
enemy = (GameObject)Instantiate(enemyPrefab,
spawnPosition, spawnRotation);
NetworkServer.Spawn(enemy);
}
}
}
製作敵人,將場景中的Player去掉PlayerController並適當修改造型後,拖回到Project面版中並命名爲Enemy。
然後將Enemy賦予EnemySpawner並註冊到NetWorkManager中的SpawnInfo的Spanwnableprefab中(注意Enemy的NetWorkIdentity還是需要選擇LocalPlayerAuthority,才能同步到每個場景中)
十五、銷燬敵人
攻擊敵人到血量爲0是,要上敵人銷燬,只需要將Health做適當修改:(提供destroyOnDeath可選擇)
using
UnityEngine;
using
UnityEngine.UI;
using
UnityEngine.Networking;
using
System.Collections;
public
class
Health
:
NetworkBehaviour
{
public
const
int
maxHealth = 100;
public
bool
destroyOnDeath;
[SyncVar(hook
=
"OnChangeHealth")]
public
int
currentHealth = maxHealth;
public
RectTransform
healthBar;
public
void
TakeDamage(int
amount)
{
if
(!isServer)
return;
currentHealth -= amount;
if
(currentHealth <= 0)
{
if
(destroyOnDeath)
{
Destroy(gameObject);
}
else
{
currentHealth = maxHealth;
// called on the Server, will be invoked on the Clients
RpcRespawn();
}
}
}
void
OnChangeHealth(int
currentHealth)
{
healthBar.sizeDelta =
new
Vector2(currentHealth, healthBar.sizeDelta.y);
}
[ClientRpc]
void
RpcRespawn()
{
if
(isLocalPlayer)
{
// Set the player’s position to origin
transform.position =
Vector3.zero;
}
}
}
十六、生成與再生成
遊戲對象生成點的控制可以利用
NetworkStartPosition 來實現,只需要在從預先設置好座標點,當Player生成時,就可以選擇性的在這些點之間進行生成。
同時,NetworkManager身上有 Random and Round Robin兩和生成方式,分別是隨機在生成點之間進行生成,和順序在這些點之間生成。
當需要重新設置生成點,則需要用用戶自行找到生成點。
using
UnityEngine;
using
UnityEngine.UI;
using
UnityEngine.Networking;
using
System.Collections;
public
class
Health
:
NetworkBehaviour
public
const
int
maxHealth = 100;
public
bool
destroyOnDeath;
[SyncVar(hook
=
"OnChangeHealth")]
public
int
currentHealth = maxHealth;
public
RectTransform
healthBar;
private
NetworkStartPosition[] spawnPoints;
void
Start()
{
if
(isLocalPlayer)
{
spawnPoints = FindObjectsOfType<NetworkStartPosition>();
}
}
public
void
TakeDamage(int
amount)
{
if
(!isServer)
return;
currentHealth -= amount;
if
(currentHealth <= 0)
{
if
(destroyOnDeath)
{
Destroy(gameObject);
}
else
{
currentHealth = maxHealth;
// called on the Server, invoked on the Clients
RpcRespawn();
}
}
}
void
OnChangeHealth(int
currentHealth)
{
healthBar.sizeDelta =
new
Vector2(currentHealth, healthBar.sizeDelta.y);
}
[ClientRpc]
void
RpcRespawn()
{
if
(isLocalPlayer)
{
// Set the spawn point to origin as a default value
Vector3
spawnPoint =
Vector3.zero;
// If there is a spawn point array and the array is not empty, pick one at random
if
(spawnPoints !=
null
&& spawnPoints.Length > 0)
{
spawnPoint = spawnPoints[Random.Range(0,
spawnPoints.Length)].transform.position;
}
// Set the player’s position to the chosen spawn point
transform.position = spawnPoint;
}
}