前言
随着Unity版本的更新,新版的网络系统Multiplayer也渐渐地越来越被重视,5.3.4版本测试很好用。使用这套网络系统可以轻松开发联机网络游戏,而且其中封装的API也针对于开发者的层次作了区分。HighLevelAPI(简称HLAPI)针对于简单的网络系统搭建,封装的比较严重,只需轻松几部即可完成网络环境的搭建;LowLevelAPI(简称LLAPI)偏向底层,网络环境的搭建,需要依靠底层类层层搭建,但较为灵活。根据不同的需求,可以选择不同的API,当然通常HLAPI和LLAPI是混合起来一起用的。本文会简单讲解Multiplayer的API架构,主要通过项目将所有内容串联。
-
HLAPI架构图
首先给大家看一下UnityAPI中提供的一张Multiplayer的HLAPI架构图,清晰的了解我们常用的类的层次。
- Transport/Configuration — 底层API类
- Connection/Reader/Writer — 消息发送类、序列化与反序列化类
- NetworkClient/NetworkServer — 网络环境搭建类
- NetworkIDentity/NetworkBehaviour — 网络对象状态同步
- NetworkManager — 网络游戏控制(一个组件搞定一个网络)
- NetworkLobbyManager — 集成了网络游戏大厅功能
- NetworkTransform/NetworkAnimator — 引擎继承的状态同步组件
-
使用基础类(NetworkServer/NetworkClient)搭建网络环境
服务器端
NetworkServer.Listen(7777);//创建服务器监听本机网卡7777端口客户端
NetworkClient client;//创建客户端对象
client.Connect("127.0.0.1",7777);//连接服务器
-
使用基础类(NetworkServer/NetworkClient)创建网络游戏对象
- 客户端
ClientScene.Ready(msg.conn); //通知服务器已准备完毕
ClientScene.RegisterPrefab(playerPrefab); //注册网络预设体
ClientScene.AddPlayer(0); //通知服务器实例化预设体 - 服务器(只有服务器才能创建网络对象)
GameObject player = (GameObject)Instantiate(playerPrefab);
//给予该客户端该对象的权限
NetworkServer.AddPlayerForConnection(netMsg.conn, player, 0);
//卵生[同步到其他客户端]
NetworkServer.Spawn(player);
- 客户端
-
远程过程调用(RPC)
Command:由客户端发送给服务器[在服务器执行方法]
ClientRPC:由服务器发送给客户端[在客户端执行方法]
客户端调服务器方法(Command方法名必须以Cmd开头)
[Command]
///
/// 发射炮弹
///
void CmdFire ()
{
GameObject bullet = (GameObject)Instantiate (MyLobbyManager.instance.spawnPrefabs [0],firePoint.position, firePoint.rotation);
bullet.GetComponent().velocity = bullet.transform.forward * 20;
NetworkServer.Spawn (bullet);
}服务器调客户端方法(ClientRPC方法必须以Rpc开头)
[ClientRpc]
///
/// 播放特效稍后销毁
///
/// Eff.
void RpcStopEffect (GameObject eff)
{
eff.GetComponent().Play ();
Destroy (eff, 1.05f);
}
当然是用NetworkManager/NetworkLobbyManager组件同样可以搭建网络环境,且更为方便,这里不再赘述,详见项目。
-
实战项目坦克大战
挑几个重点脚本看看
1.大厅管理
using UnityEngine;
using System.Collections;
using UnityEngine.Networking;
using UnityEngine.SceneManagement;
public class MyLobbyManager : NetworkLobbyManager
{
//单例
public static MyLobbyManager instance;
//是否开启切换场景标志位
public bool beginChange = false;
//背景音乐
public GameObject backAud;
//玩家位置编号
private int playerPositionIndex = 0;
void Awake ()
{
instance = this;
}
void Start ()
{
DontDestroyOnLoad (backAud);
}
///
/// 当所有玩家都已准备完毕
///
public override void OnLobbyServerPlayersReady ()
{
//启动协程等待动画播放完毕
StartCoroutine (PlayProgress ());
//遍历所有客户端发送播放指令
foreach (NetworkLobbyPlayer item in lobbySlots) {
if (item) {
(item as MyLobbyPlayer).RpcBeginPlay ();
}
}
}
IEnumerator PlayProgress ()
{
//如果还没有开始切换场景,继续播放动画,保持等待
while (!beginChange) {
yield return null;
}
base.OnLobbyServerPlayersReady ();
}
//当服务器添加玩家对象时调用
public override void OnServerAddPlayer (NetworkConnection conn, short playerControllerId)
{
base.OnServerAddPlayer (conn, playerControllerId);
//判断是否在游戏场景而非游戏大厅
if (beginChange) {
//创建坦克
GameObject player = Instantiate (gamePlayerPrefab) as GameObject;
//通过新场景的NetworkStartPosition确定坦克的创建位置
player.transform.position = startPositions [playerPositionIndex++].position;
//设置坦克脚本中的网络变量--坦克编号
player.GetComponent ().tankNum = playerPositionIndex - 1;
//给予客户端该坦克的使用权限
NetworkServer.AddPlayerForConnection (conn, player, playerControllerId);
//卵生坦克
NetworkServer.Spawn (player);
}
}
}
2.大厅玩家
using UnityEngine;
using System.Collections;
using UnityEngine.Networking;
using UnityEngine.UI;
public class MyLobbyPlayer : NetworkLobbyPlayer
{
//玩家大厅名称显示
private Transform content;
//玩家大厅准备按钮
private Button readyButton;
//单例LobbyManager
private MyLobbyManager manager;
//倒计时进度条
private GameObject progress;
void Awake ()
{
manager = MyLobbyManager.instance;
content = GameController.instance.content.transform;
readyButton = transform.GetChild (0).GetComponent
3.主场景玩家(坦克)
using UnityEngine;
using System.Collections;
using UnityEngine.Networking;
using UnityEngine.UI;
public class MyPlayer : NetworkBehaviour
{
[SyncVar]
//坦克编号
public int tankNum = 0;
[SyncVar]
//坦克血量
public int health = 100;
//坦克移动速度
public float tankMoveSpeed = 3f;
//坦克旋转速度
public float tankTurnSpeed = 10f;
//坦克发射的炮弹飞行速度
public float fireSpeed = 20f;
//声音片段
public AudioClip idle;
public AudioClip run;
private Rigidbody rig;
//观察点
private Transform targetPoint;
//坦克炮头
private Transform gun;
//发射点
private Transform firePoint;
//操纵轴
private float hor, ver, gunDir;
//坦克血条颜色
private Color[] colors = new Color[]{ Color.red, Color.green };
//坦克血条背景图片
private Image healthColor;
//坦克血条
private Slider healthSlider;
//结果UI
private GameObject resultUI;
//声音片段
private AudioSource aud;
void Awake ()
{
rig = GetComponent ();
aud = GetComponent ();
targetPoint = transform.Find ("TargetPoint");
gun = transform.Find ("TankTurret");
firePoint = transform.Find ("TankTurret/FirePoint");
healthColor = transform.Find ("HealthCanvas/Slider/Fill Area/Fill").GetComponent ();
healthSlider = transform.Find ("HealthCanvas/Slider").GetComponent ();
resultUI = GameObject.FindWithTag ("UI");
}
///
/// 本地玩家Start触发
///
public override void OnStartLocalPlayer ()
{
//如果是本地玩家
if (isLocalPlayer) {
//设置摄像机跟踪点
Camera.main.GetComponent ().SetTarget (targetPoint);
}
}
[ClientCallback]
void Update ()
{
//设置血条背景颜色
healthColor.color = colors [tankNum];
//设置血条值
healthSlider.value = health;
//如果是本地玩家
if (isLocalPlayer) {
//操纵坦克
hor = Input.GetAxis ("Horizontal");
ver = Input.GetAxis ("Vertical");
gunDir = Input.GetAxis ("GunDirection");
rig.MovePosition (transform.position + transform.forward * ver * Time.deltaTime * tankMoveSpeed);
transform.eulerAngles += Vector3.up * hor * tankTurnSpeed;
gun.transform.eulerAngles += Vector3.up * gunDir * tankTurnSpeed;
//如果坦克移动
if (hor != 0 || ver != 0) {
if (aud.clip == idle) {
aud.Stop ();
aud.clip = run;
} else {
if (!aud.isPlaying) {
aud.Play ();
}
}
} else {
if (aud.clip == run) {
aud.Stop ();
aud.clip = idle;
} else {
if (!aud.isPlaying) {
aud.Play ();
}
}
}
//发射炮弹
if (Input.GetKeyDown (KeyCode.Space)) {
CmdFire ();
}
}
//如果血量见底
if (health <= 0) {
//本地玩家失败
if (isLocalPlayer) {
resultUI.transform.GetChild (0).gameObject.SetActive (true);
resultUI.transform.GetChild (0).GetChild (0).GetComponent ().text = "GameOver";
resultUI.transform.GetChild (0).GetChild (1).GetComponent ().text = "GameOver";
}
//非本地玩家胜利
else {
resultUI.transform.GetChild (0).gameObject.SetActive (true);
resultUI.transform.GetChild (0).GetChild (0).GetComponent ().text = "Victory";
resultUI.transform.GetChild (0).GetChild (1).GetComponent ().text = "Victory";
}
}
}
[Command]
///
/// 发射炮弹
///
void CmdFire ()
{
GameObject bullet = (GameObject)Instantiate (MyLobbyManager.instance.spawnPrefabs [0],
firePoint.position, firePoint.rotation);
bullet.GetComponent ().velocity = bullet.transform.forward * 20;
NetworkServer.Spawn (bullet);
}
}
结束语
相比从前的老网络系统,新版网络解决了很多Bug,也进一步做了优化,没有出现老网络的尴尬问题,只是新网络同步帧速率有些低,有时候会出现延迟较大的情况,这方面还有待改进。新网络类多内容也多,感兴趣的同学还需要多去看API,关于新网络今后还会有续集喔,敬请期待。本次项目链接: http://pan.baidu.com/s/1jHMN9zS 密码: wh3n