一、创建场景。
命名场景为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;
}
}