Unity学习笔记09联网
网络基础
多为策略游戏:MMORTS 角色扮演游戏:MMORPG
联网功能
1.现在的游戏引擎都提供了基本的联网功能,比如LAN方式的联网对战。
2.大规模多人在线游戏的网络处理很复杂。
局域网
一般没有专用的服务器,通常是一台机器充当两个角色,其他机器连接到该机器上,构成局域网,稳定性
比较高。
广域网
一般有一台专用的,性能极高的作为服务器,其他所有的机器通过互联网远程连接到该机器上。
游戏联网的网络架构(广域网下):一般是这两种

分组交换技术
互联网是一种分组交换并且容错的网络系统
1.分组交换(信息被分解为多个小的包(几个字节或千字节的大小)分别发送出去。
2.容错(数据包在网络出现错误或者服务器发生故障的情况下仍然可以发送,如果一个服务器出现故障,数据包将
使用其他路径到达目的地)
互联网的协议
可以分为3层
1,最底层的是IP协议(用于报文交换网络的一种面向数据的协议,定义了数据包在网际传送的格式)
2,UDP协议或者TCP协议。
UDP或者TCP层将大的数据包传给IP,IP将数据包分割为小的子数据包,为每个数据包加上一个信封再发送出去

3,最顶层的是一些应用层协议。
推荐书籍:

联网功能实现:使用游戏引擎自带的联网模块或者使用底层网络接口自己实现功能
玩家连线(联网)
游戏联网需要的几个概念
1.网络管理器(管理联网物体)
2.联网用户界面
3.具备联网功能的玩家预制件
4.具有联网能力的脚本和游戏物体
客户端,服务器
客户端:连接到服务器的游戏实例
服务器:专用服务器(广域网),主机服务器(局域网)
简单实践
在unity的Package Manager中下载安装Multiplayer HLAPI。
新建一个空物体,为其添加NetworkManager和NetworkManagerHUD组件。
制作用来联网的预制件,为其添加NetworkIdentity(需要将Local Player Authority点上,组件是用来识别联网的物
体)组件和NetworkTransfrom(可以自动的将物体的变换在网络之间进行同步传输)。还添加Network Animator
组件(可以自动的将物体的动画在网络之间进行同步传输)。但需要一个动画控制器,也可以直接将预制件拖拽过
去,勾选所需的属性即可。
修改脚本的联网功能,将预制件拖拽给NetworkManager的Player Prefab。
运行游戏后,点击LAN Host(H)生成一个本地主机,要联网的话至少需要两个机器来联网,可以将当前场景再生成
一个副本(file--build and run,需要为其选择文件存储位置),然后运行两个游戏,一个作为服务器,一个作为客
户端,生成角色后可以在对方的场景里看到。
玩家角色比较特殊, 每个玩家控制自己本地的实例,然后由服务器端来完成同步
游戏物体同步
网络权限Network Authority
1.客户端和服务器都可以控制游戏物体行为
2.网络权限指的是游戏物体如何以及在哪里被管理
3.默认是服务器端(玩家角色特殊,是在客户端)
动态生成游戏物体
1.非联网物体使用GameObject.Instantiate生成物体(动态生成游戏物体:先制作好预制件,然后使用这个函数)
2.而联网物体需要由服务器端来生成(使用spawn方式生成,生成的物体交由Network Manager管理)
联网和非联网游戏物体
联网游戏的场景中包含联网和非联网物体
1.联网的物体需要在全网同步。
2.非联网的物体只运行于本地(静态物体,比如石块、楼梯。不需要同步的物体,比如随风摆动的树木、草丛或者
云朵)。
联网物体
1.需要包含Network ldentity组件。
2.还需要开发人员指定物体的哪些属性需要同步。
3.哪些属性需要同步取决于游戏内容(对于玩家或者非玩家角色的位置、朝向。带有动画物体的动画状态。—些数
值,比如游戏剩余时长、玩家的能量值)
属性同步
1.可以自动同步的属性
Network Transform组件来同步物体变换属性。Network Animator组件来同步动画属性。
2.无法自动同步的属性(在脚本里面指定)
简单实践
Network transfrom中的Network send rate 调整为零,代表刚开始时自动同步,之后则通过脚本来控制同步。如
果完全自动同步的话对网络的要求很高。
新建一个子弹预制件拖拽给网络管理器的Registered Spawnable Prefabs(代表可网络生成的物体)
修改玩家控制脚本,让角色可以发射子弹,修改后记得将预制件赋给该脚本
using System;
using UnityEngine;
using UnityStandardAssets.CrossPlatformInput;
using UnityEngine.Networking;
namespace UnityStandardAssets.Characters.ThirdPerson
{
[RequireComponent(typeof(ThirdPersonCharacter))]
public class ThirdPersonUserControl : NetworkBehaviour
{
private ThirdPersonCharacter m_Character; // A reference to the ThirdPersonCharacter on the object
private Transform m_Cam; // A reference to the main camera in the scenes transform
private Vector3 m_CamForward; // The current forward direction of the camera
private Vector3 m_Move;
private bool m_Jump; // the world-relative desired move direction, calculated from the camForward and user input.
//发射子弹
public GameObject bulletPrefab;
private void Start()
{
// get the transform of the main camera
if (Camera.main != null)
{
m_Cam = Camera.main.transform;
}
else
{
Debug.LogWarning(
"Warning: no main camera found. Third person character needs a Camera tagged \"MainCamera\", for camera-relative controls.", gameObject);
// we use self-relative controls in this case, which probably isn't what the user wants, but hey, we warned them!
}
// get the third person character ( this should never be null due to require component )
m_Character = GetComponent<ThirdPersonCharacter>();
}
private void Update()
{
if (!isLocalPlayer)//如果不是本地要控制的玩家角色的话,就不进行后面的处理
{
return;
}
if (!m_Jump)
{
m_Jump = CrossPlatformInputManager.GetButtonDown("Jump");
}
}
// Fixed update is called in sync with physics
private void FixedUpdate()
{
if (!isLocalPlayer)//如果不是本地要控制的玩家角色的话,就不进行后面的处理
{
return;
}
// read inputs
float h = CrossPlatformInputManager.GetAxis("Horizontal");
float v = CrossPlatformInputManager.GetAxis("Vertical");
bool crouch = Input.GetKey(KeyCode.C);
// calculate move direction to pass to character
if (m_Cam != null)
{
// calculate camera relative direction to move:
m_CamForward = Vector3.Scale(m_Cam.forward, new Vector3(1, 0, 1)).normalized;
m_Move = v * m_CamForward + h * m_Cam.right;
}
else
{
// we use world-relative directions in the case of no main camera
m_Move = v * Vector3.forward + h * Vector3.right;
}
#if !MOBILE_INPUT
// walk speed multiplier
if (Input.GetKey(KeyCode.LeftShift)) m_Move *= 0.5f;
#endif
// pass all parameters to the character control script
m_Character.Move(m_Move, crouch, m_Jump);
m_Jump = false;
//
if (Input.GetMouseButtonDown(0))
{
CmdFire();
}
}
public void Fire()
{
var bullet = Instantiate(bulletPrefab, transform.position + transform.up + transform.forward * 2,Quaternion.identity) as GameObject;
}
[Command]//加上这个前缀代表下面的函数是由客户端发起调用,但是在服务器端执行
public void CmdFire()//必须加Cmd前缀,,,子弹联网
{
var bullet = Instantiate(bulletPrefab, transform.position + transform.up + transform.forward * 2, transform.rotation) as GameObject;
NetworkServer.Spawn(bullet);//生成并同步到各客户端
}
}
}
玩家生命值脚本,赋给玩家预制件
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;
public class Health : NetworkBehaviour//有关网络的操作需要继承该类
{
private static int hp_max = 100;//最大生命值
public int hp = hp_max;//初始生命值
public void GetDamage(int dmg)
{
if(!isServer)
{
return;
}
hp -= dmg;
if(hp<0)
{
RpcRespawn();//生命值小于0时重置玩家
}
}
[ClientRpc]//表示下面的函数是由服务器发起调用的,但是在客户端来执行
public void RpcRespawn()//必须加上Rpc前缀
{
if(isLocalPlayer)
{
transform.position = Vector3.zero;//将位置重置为原点
hp = hp_max;
}
}
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
}
}
子弹脚本,赋给子弹预制件
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class NetBullet : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
}
private void OnTriggerEnter(Collider other)//子弹预制件的Is trigger要点上
{
other.SendMessage("GetDamage",60,SendMessageOptions.DontRequireReceiver);//如果碰到的物体身上有GetDamage的话,执行健康值减少60的函数,因为有可能打到其他的物体
}
}
角色同步
(非玩家角色,每一个客户端并不具备直接控制这些角色的能力,非玩家角色直接运行于服务器端,只有服务器端
才能对其进行控制以及网络同步)
简单实践
建立敌人预制件和脚本。
Instantiate
使用预制件在场景中生成物体,共有三个参数,一是预制件,二是生成物体的位置,三是生成物体的朝向。
示例:
var enemy = Instantiate(enemyProfab, new Vector3(unit_pos.x, 0, unit_pos.y), rnd_rot);
生成敌人的脚本
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;
public class EnemySpawner : NetworkBehaviour
{
public GameObject enemyProfab;//敌人预制件
public int enemyNum = 5;//生成的敌人数量
public override void OnStartServer()//服务器端开始运行的时候生成敌人
{
for(int i=0;i<enemyNum;++i)
{
var unit_pos = Random.insideUnitCircle;//随机生成位置,使用这个函数是生成在一个单位圆内,unit_pos代表一个二维向量类型
unit_pos = unit_pos * 6;//表示单位为6
var rnd_rot = Quaternion.Euler(new Vector3(0, Random.Range(-180, 180), 0));//生成一个随机的朝向
var enemy = Instantiate(enemyProfab, new Vector3(unit_pos.x, 0, unit_pos.y), rnd_rot);
NetworkServer.Spawn(enemy);//由服务器端生成敌人
}
}
}
生成后要将敌人预制件拖拽上去。
NetworkIdentity中的Server Only 如果勾选则表示阻止客户端有权利生成公用的敌人。
修改Health脚本,让子弹可以打到敌人,并且需要将敌人预制件的IsEnemy点上,表示是敌人,不是玩家
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;
public class Health : NetworkBehaviour//有关网络的操作需要继承该类
{
private static int hp_max = 100;//最大生命值
public int hp = hp_max;//初始生命值
public bool isEnemy = false;//标识角色是不是敌人
public void GetDamage(int dmg)
{
if(!isServer)
{
return;
}
hp -= dmg;
if(hp<0)
{
if (isEnemy) Destroy(gameObject);//如果敌人血量小于0让其消失
RpcRespawn();//生命值小于0时重置玩家
}
}
[ClientRpc]//表示下面的函数是由服务器发起调用的,但是在客户端来执行
public void RpcRespawn()//必须加上Rpc前缀
{
if(isLocalPlayer)
{
transform.position = Vector3.zero;//将位置重置为原点
hp = hp_max;
}
}
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
}
}